Revision: 10456
Author:   unn...@google.com
Date:     Thu Jul 14 10:42:16 2011
Log:      Add chunking to the xsiframe linker

Review at http://gwt-code-reviews.appspot.com/1477802

http://code.google.com/p/google-web-toolkit/source/detail?r=10456

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/installScriptAlreadyIncluded.js /trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js
 /trunk/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
 /trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
 /trunk/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
 /trunk/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml
 /trunk/user/test/com/google/gwt/core/ext/test/LinkerTest.java

=======================================
--- /trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java Thu Jun 23 07:36:26 2011 +++ /trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java Thu Jul 14 10:42:16 2011
@@ -26,6 +26,7 @@
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.SoftPermutation;
+import com.google.gwt.core.ext.linker.StatementRanges;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.TextOutput;
 import com.google.gwt.dev.util.Util;
@@ -41,6 +42,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
+import java.util.SortedSet;
 import java.util.TreeMap;

 /**
@@ -54,6 +56,11 @@
    * goes away?
    */

+  /**
+ * A configuration property indicating how large each script tag should be.
+   */
+ private static final String CHUNK_SIZE_PROPERTY = "iframe.linker.script.chunk.size";
+
   /**
    * File name for computeScriptBase.js.
    */
@@ -93,6 +100,56 @@
       buf.replace(pos, pos + len, replace);
     }
   }
+
+  /**
+ * Split a JavaScript string into multiple chunks, at statement boundaries.
+   * This method is made default access for testing.
+   *
+   * @param ranges Describes where the statements are located within the
+ * JavaScript code. If <code>null</code>, then return <code>js</code>
+   *          unchanged.
+   * @param js The JavaScript code to be split up.
+ * @param charsPerChunk The number of characters to be put in each script tag.
+   * @param scriptChunkSeparator The string to insert between chunks.
+   */
+ public static String splitPrimaryJavaScript(StatementRanges ranges, String js,
+      int charsPerChunk, String scriptChunkSeparator) {
+    if (charsPerChunk < 0 || ranges == null) {
+      return js;
+    }
+
+    StringBuilder sb = new StringBuilder();
+    int bytesInCurrentChunk = 0;
+
+    for (int i = 0; i < ranges.numStatements(); i++) {
+      int start = ranges.start(i);
+      int end = ranges.end(i);
+      int length = end - start;
+ if (bytesInCurrentChunk > 0 && bytesInCurrentChunk + length > charsPerChunk) {
+        if (lastChar(sb) != '\n') {
+          sb.append('\n');
+        }
+        sb.append(scriptChunkSeparator);
+        bytesInCurrentChunk = 0;
+      }
+      if (bytesInCurrentChunk > 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);
+      bytesInCurrentChunk += length;
+    }
+    return sb.toString();
+  }
+
+  private static char lastChar(StringBuilder sb) {
+    return sb.charAt(sb.length() - 1);
+  }

   /**
    * This method is left in place for existing subclasses of
@@ -139,7 +196,27 @@
   public boolean supportsDevModeInJunit(LinkerContext context) {
     return (getHostedFilename() != "");
   }
-
+
+  /**
+ * Extract via {@link #CHUNK_SIZE_PROPERTY} the number of characters to be
+   * included in each chunk.
+   */
+  protected int charsPerChunk(LinkerContext context, TreeLogger logger) {
+ SortedSet<ConfigurationProperty> configProps = context.getConfigurationProperties();
+    for (ConfigurationProperty prop : configProps) {
+      if (prop.getName().equals(CHUNK_SIZE_PROPERTY)) {
+        return Integer.parseInt(prop.getValues().get(0));
+      }
+    }
+ // CompilerParameters.gwt.xml indicates that if this property is -1, then
+    // no chunking is performed, so we return that as the default.  Since
+ // Core.gwt.xml contains a definition for this property, this should never + // happen in production, but some tests mock out the ConfigurationProperties + // so we want to have a reasonable default rather than making them all add
+    // a value for this property.
+    return -1;
+  }
+
   protected Collection<Artifact<?>> doEmitCompilation(TreeLogger logger,
LinkerContext context, CompilationResult result, ArtifactSet artifacts)
       throws UnableToCompleteException {
@@ -235,8 +312,10 @@
       LinkerContext context, CompilationResult result, String[] js,
       ArtifactSet artifacts) throws UnableToCompleteException {
     TextOutput to = new DefaultTextOutput(context.isOutputCompact());
+ String temp = splitPrimaryJavaScript(result.getStatementRanges()[0], js[0], + charsPerChunk(context, logger), getScriptChunkSeparator(logger, context));
     to.print(generatePrimaryFragmentString(
-        logger, context, result, js[0], js.length, artifacts));
+        logger, context, result, temp, js.length, artifacts));
     return Util.getBytes(to.toString());
   }

@@ -325,7 +404,16 @@

   protected abstract String getModuleSuffix(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException;
-
+
+  /**
+ * Some subclasses support "chunking" of the primary fragment. If chunking will + * be supported, this function should be overridden to return the string which
+   * should be inserted between each chunk.
+   */
+ protected String getScriptChunkSeparator(TreeLogger logger, LinkerContext context) {
+    return "";
+  }
+
   protected abstract String getSelectionScriptTemplate(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException;

=======================================
--- /trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js Thu Jun 23 07:36:26 2011 +++ /trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js Thu Jul 14 10:42:16 2011
@@ -9,14 +9,18 @@

   function installCode(code) {
     var docbody = getInstallLocation();
-    var script = getInstallLocationDoc().createElement('script');
-    script.language='javascript';
-    script.text = code;
-    docbody.appendChild(script);
-
-    // Remove the tags to shrink the DOM a little.
-    // It should have installed its code immediately after being added.
-    docbody.removeChild(script);
+    for (var i = 0; i < code.length; i++) {
+      var script = getInstallLocationDoc().createElement('script');
+      script.language='javascript';
+      script.text = code[i];
+      docbody.appendChild(script);
+
+ // Unless we're in pretty mode, remove the tags to shrink the DOM a little.
+      // It should have installed its code immediately after being added.
+      __START_OBFUSCATED_ONLY__
+      docbody.removeChild(script);
+      __END_OBFUSCATED_ONLY__
+    }
   }

   // Set up a script tag to start downloading immediately, as well as a
=======================================
--- /trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js Thu Jun 23 07:36:26 2011 +++ /trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js Thu Jul 14 10:42:16 2011
@@ -8,16 +8,18 @@

   function installCode(code) {
     var docbody = getInstallLocation();
-    var script = getInstallLocationDoc().createElement('script');
-    script.language='javascript';
-    script.text = code;
-    docbody.appendChild(script);
-
- // Unless we're in pretty mode, remove the tags to shrink the DOM a little.
-    // It should have installed its code immediately after being added.
-    __START_OBFUSCATED_ONLY__
-    docbody.removeChild(script);
-    __END_OBFUSCATED_ONLY__
+    for (var i = 0; i < code.length; i++) {
+      var script = getInstallLocationDoc().createElement('script');
+      script.language='javascript';
+      script.text = code[i];
+      docbody.appendChild(script);
+
+ // Unless we're in pretty mode, remove the tags to shrink the DOM a little.
+      // It should have installed its code immediately after being added.
+      __START_OBFUSCATED_ONLY__
+      docbody.removeChild(script);
+      __END_OBFUSCATED_ONLY__
+    }
   }

   // Set up a script tag to start downloading immediately, as well as a
=======================================
--- /trunk/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java Thu Jun 23 07:36:26 2011 +++ /trunk/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java Thu Jul 14 10:42:16 2011
@@ -385,6 +385,11 @@

     return out.toString();
   }
+
+  @Override
+ protected String getScriptChunkSeparator(TreeLogger logger, LinkerContext context) { + return shouldInstallCode(context) ? "__SCRIPT_CHUNK_SEPARATOR_MARKER__" : "";
+  }

   @Override
protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context) {
@@ -511,9 +516,17 @@
       // Rewrite the code so it can be installed with
       // __MODULE_FUNC__.onScriptDownloaded
       out.append(context.getModuleFunctionName());
-      out.append(".onScriptDownloaded(");
-      out.append(JsToStringGenerationVisitor.javaScriptString(script));
-      out.append(")");
+      out.append(".onScriptDownloaded([");
+ String[] chunks = script.split(getScriptChunkSeparator(logger, context));
+      boolean first = true;
+      for (String chunk : chunks) {
+        if (!first) {
+          out.append(", ");
+        }
+        out.append(JsToStringGenerationVisitor.javaScriptString(chunk));
+        first = false;
+      }
+      out.append("])");
     } else {
       out.append(script);
     }
=======================================
--- /trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java Thu Jun 23 07:36:26 2011 +++ /trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java Thu Jul 14 10:42:16 2011
@@ -20,18 +20,14 @@
 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.LinkerOrder;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.core.ext.linker.Shardable;
-import com.google.gwt.core.ext.linker.StatementRanges;
 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.util.SortedSet;
-
 /**
* Implements the canonical GWT bootstrap sequence that loads the GWT module in
  * a separate iframe.
@@ -44,61 +40,6 @@
    * 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.
-   *
-   * @param ranges Describes where the statements are located within the
- * JavaScript code. If <code>null</code>, then return <code>js</code>
-   *          unchanged.
-   * @param js The JavaScript code to be split up.
- * @param charsPerChunk The number of characters to be put in each script tag
-   */
-  static String splitPrimaryJavaScript(StatementRanges ranges, String js,
-      int charsPerChunk) {
-    if (charsPerChunk < 0 || ranges == null) {
-      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() {
@@ -120,7 +61,7 @@
     StringBuffer b = new StringBuffer();
b.append(getModulePrefix(logger, context, result.getStrongName(), js.length));
     b.append(splitPrimaryJavaScript(result.getStatementRanges()[0], js[0],
-        charsPerChunk(context, logger)));
+ charsPerChunk(context, logger), getScriptChunkSeparator(logger, context)));
     b.append(getModuleSuffix(logger, context));
     return Util.getBytes(b.toString());
   }
@@ -167,33 +108,20 @@

     return out.toString();
   }
+
+  @Override
+ protected String getScriptChunkSeparator(TreeLogger logger, LinkerContext context) {
+    return SCRIPT_CHUNK_SEPARATOR;
+  }

   @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();
-  }

   /**
* This is the real implementation of <code>getModulePrefix</code> for this
=======================================
--- /trunk/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java Fri Jun 12 15:49:54 2009 +++ /trunk/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java Thu Jul 14 10:42:16 2011
@@ -16,6 +16,7 @@
 package com.google.gwt.core.linker;

 import com.google.gwt.core.ext.linker.StatementRanges;
+import com.google.gwt.core.ext.linker.impl.SelectionScriptLinker;
 import com.google.gwt.core.ext.linker.impl.StandardStatementRanges;

 import junit.framework.TestCase;
@@ -23,7 +24,7 @@
 import java.util.ArrayList;

 /**
- * Tests the script chunking in the {@link IFrameLinker}.
+ * Tests the script chunking in the {@link SelectionScriptLinker}.
  */
 public class ScriptChunkingTest extends TestCase {
   /**
@@ -71,12 +72,37 @@
     builder.addStatement(stmt4);
     builder.addNonStatement("}");

-    String split = IFrameLinker.splitPrimaryJavaScript(builder.getRanges(),
-        builder.getJavaScript(), stmt1.length() + stmt2.length());
+ String split = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(),
+        builder.getJavaScript(), stmt1.length() + stmt2.length(),
+        IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
assertEquals(stmt1 + stmt2 + IFrameLinker.SCRIPT_CHUNK_SEPARATOR + stmt3
         + ';' + stmt4, split);
   }

+
+  /**
+   * Test that with the default chunk separator (""), splitting is a no-op.
+   */
+  public void testEmptyStringSeparator() {
+    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 = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(),
+        builder.getJavaScript(), stmt1.length() + stmt2.length(), "");
+    assertEquals(stmt1 + stmt2 + stmt3 + ';' + stmt4, split);
+  }
+
   /**
    * Test a chunk size large enough that no splitting happens.
    */
@@ -95,8 +121,8 @@
     builder.addStatement(stmt4);
     builder.addNonStatement("}");

-    String split = IFrameLinker.splitPrimaryJavaScript(builder.getRanges(),
-        builder.getJavaScript(), 10000);
+ String split = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(), + builder.getJavaScript(), 10000, IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
     assertEquals(stmt1 + stmt2 + stmt3 + ';' + stmt4, split);
   }

@@ -114,8 +140,8 @@
     builder.addStatement("x=5");
     builder.addNonStatement("}");

-    String split = IFrameLinker.splitPrimaryJavaScript(builder.getRanges(),
-        builder.getJavaScript(), -1);
+ String split = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(),
+        builder.getJavaScript(), -1, IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
     assertEquals(builder.getJavaScript(), split);
   }

@@ -133,8 +159,8 @@
     builder.addStatement("x=5");
     builder.addNonStatement("}");

-    String split = IFrameLinker.splitPrimaryJavaScript(null,
-        builder.getJavaScript(), 5);
+    String split = SelectionScriptLinker.splitPrimaryJavaScript(null,
+        builder.getJavaScript(), 5, IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
     assertEquals(builder.getJavaScript(), split);
   }
 }
=======================================
--- /trunk/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml Wed Mar 3 09:01:30 2010 +++ /trunk/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml Thu Jul 14 10:42:16 2011
@@ -17,4 +17,7 @@
 <module>
        <inherits name='com.google.gwt.junit.JUnit' />
        <source path='test' />
+    <set-configuration-property name="iframe.linker.script.chunk.size"
+    value="100" />
+
 </module>
=======================================
--- /trunk/user/test/com/google/gwt/core/ext/test/LinkerTest.java Tue Nov 18 10:54:35 2008 +++ /trunk/user/test/com/google/gwt/core/ext/test/LinkerTest.java Thu Jul 14 10:42:16 2011
@@ -16,6 +16,8 @@
 package com.google.gwt.core.ext.test;

 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RootPanel;

 /**
  * Tests that all the linkers work well enough to run the
@@ -25,4 +27,9 @@
   public void testSomethingTrivial() {
     assertTrue(true);
   }
-}
+
+  public void testSomethingBigEnoughToTriggerChunking() {
+    RootPanel.get().add(new Label("Hello there"));
+    assertEquals(1, RootPanel.get().getWidgetCount());
+  }
+}

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

Reply via email to