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
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5496 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
index e042f5a..89df87c 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
@@ -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.
*/
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/StatementRanges.java b/dev/core/src/com/google/gwt/core/ext/linker/StatementRanges.java
new file mode 100644
index 0000000..30f919f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/StatementRanges.java
@@ -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);
+}
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 8fd3d90..62a8cf9 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
@@ -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());
- }
}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
index 3a99ca3..3b2d540 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
@@ -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;
}
/**
@@ -115,6 +119,11 @@
}
@Override
+ public StatementRanges[] getStatementRanges() {
+ return statementRanges;
+ }
+
+ @Override
public String getStrongName() {
return strongName;
}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 3ccba69..48d3201 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -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);
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
new file mode 100644
index 0000000..aba199a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
@@ -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];
+ }
+}
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 e11d31b..a16df55 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -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,13 +213,35 @@
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();
+ }
+
/**
* This is the real implementation of <code>getModulePrefix</code> for this
* linker. The other versions forward to this one.
diff --git a/dev/core/src/com/google/gwt/dev/PermutationResult.java b/dev/core/src/com/google/gwt/dev/PermutationResult.java
index 77cabf5..c1cba6d 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationResult.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationResult.java
@@ -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();
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index f440994..989574c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -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.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 45db80e..534abf3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -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,
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java
index bd2ae01..0a6f013 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java
@@ -30,6 +30,10 @@
this.textOutput = textOutput;
}
+ public int getPosition() {
+ return textOutput.getPosition();
+ }
+
public void indentIn() {
textOutput.indentIn();
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
index 080a38e..b5c7180 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
@@ -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;
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index 3b43086..71df4d5 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -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
@@ -853,6 +889,10 @@
_newlineOpt();
}
}
+ if (shouldRecordPositions) {
+ assert (statementStarts.size() == statementEnds.size() + 1);
+ statementEnds.add(p.getPosition());
+ }
}
if (needBraces) {
diff --git a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
index 0aa54c2..8e9e8b3 100644
--- a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
+++ b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
@@ -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);
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/util/TextOutput.java b/dev/core/src/com/google/gwt/dev/util/TextOutput.java
index c298c15..ecbf9d0 100644
--- a/dev/core/src/com/google/gwt/dev/util/TextOutput.java
+++ b/dev/core/src/com/google/gwt/dev/util/TextOutput.java
@@ -19,6 +19,8 @@
* Interface used for printing text output.
*/
public interface TextOutput {
+ int getPosition();
+
void indentIn();
void indentOut();
diff --git a/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java b/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
new file mode 100644
index 0000000..e2af85e
--- /dev/null
+++ b/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
@@ -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);
+ }
+}
diff --git a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
index 44cde7e..a1a1e94 100644
--- a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
+++ b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
@@ -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>
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml b/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml
index da8fd47..823bebc 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml
@@ -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>
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml b/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml
index e1de729..11f0269 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml
+++ b/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml
@@ -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>