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