Add chunking to the xsiframe linker
Review at http://gwt-code-reviews.appspot.com/1477802
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10456 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
index e9bff73..67d2cdf 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
@@ -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;
/**
@@ -55,6 +57,11 @@
*/
/**
+ * 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.
*/
protected static final String COMPUTE_SCRIPT_BASE_JS = "com/google/gwt/core/ext/linker/impl/computeScriptBaseOld.js";
@@ -95,6 +102,56 @@
}
/**
+ * 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
* SelectionScriptLinker that have not been upgraded for the sharding API.
*/
@@ -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;
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js
index 3cd3a65..2417267 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js
@@ -9,14 +9,18 @@
function installCode(code) {
var docbody = getInstallLocation();
- var script = getInstallLocationDoc().createElement('script');
- script.language='javascript';
- script.text = code;
- docbody.appendChild(script);
+ for (var i = 0; i < code.length; i++) {
+ var script = getInstallLocationDoc().createElement('script');
+ script.language='javascript';
+ script.text = code[i];
+ 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);
+ // 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
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js
index 1ee7dca..9375003 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js
@@ -8,16 +8,18 @@
function installCode(code) {
var docbody = getInstallLocation();
- var script = getInstallLocationDoc().createElement('script');
- script.language='javascript';
- script.text = code;
- docbody.appendChild(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__
+ // 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
diff --git a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
index 135b708..a863946 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -387,6 +387,11 @@
}
@Override
+ protected String getScriptChunkSeparator(TreeLogger logger, LinkerContext context) {
+ return shouldInstallCode(context) ? "__SCRIPT_CHUNK_SEPARATOR_MARKER__" : "";
+ }
+
+ @Override
protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context) {
return "com/google/gwt/core/linker/CrossSiteIframeTemplate.js";
}
@@ -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);
}
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
index 4779f65..ded6042 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -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.
@@ -45,61 +41,6 @@
*/
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() {
return "Standard";
@@ -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());
}
@@ -169,33 +110,20 @@
}
@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
* linker. The other versions forward to this one.
*/
diff --git a/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java b/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
index f9b5d53..266935d 100644
--- a/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
+++ b/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
@@ -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);
}
}
diff --git a/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml b/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml
index 7350e5c..10a1c3e 100644
--- a/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml
+++ b/user/test/com/google/gwt/core/ext/LinkerTest.gwt.xml
@@ -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>
diff --git a/user/test/com/google/gwt/core/ext/test/LinkerTest.java b/user/test/com/google/gwt/core/ext/test/LinkerTest.java
index 0f08376..2468cbf 100644
--- a/user/test/com/google/gwt/core/ext/test/LinkerTest.java
+++ b/user/test/com/google/gwt/core/ext/test/LinkerTest.java
@@ -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());
+ }
}