Re-rolling sourcemap support after rollback. Jar-jared sourcemap.jar dependency and reverted regression in StackTraceCollector.splice().
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10760 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/build.xml b/dev/build.xml
index 7318650..f586b2a 100755
--- a/dev/build.xml
+++ b/dev/build.xml
@@ -59,6 +59,7 @@
<include name="apache/ant-1.6.5.jar" />
<include name="eclipse/jdt-3.4.2.jar" />
<include name="guava/guava-r06/guava-r06-rebased-2.jar" />
+ <include name="jscomp/sourcemap-rebased.jar" />
<include name="jetty/jetty-6.1.11.jar" />
<include name="icu4j/4.4.2/icu4j.jar" />
<include name="protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar" />
@@ -111,6 +112,7 @@
<!-- htmlunit dependencies not already included: END -->
<include name="sun/swingworker/swing-worker-1.1.jar" />
<include name="guava/guava-r06/guava-r06-rebased-2.jar" />
+ <include name="jscomp/sourcemap-rebased.jar" />
</fileset>
<fileset file="build.xml"/>
</sourcefiles>
@@ -123,6 +125,7 @@
<zipfileset src="${gwt.tools.lib}/apache/ant-1.6.5.jar" />
<zipfileset src="${gwt.tools.lib}/eclipse/jdt-3.4.2.jar" />
<zipfileset src="${gwt.tools.lib}/guava/guava-r06/guava-r06-rebased-2.jar" />
+ <zipfileset src="${gwt.tools.lib}/jscomp/sourcemap-rebased.jar" />
<zipfileset src="${gwt.tools.lib}/jetty/jetty-6.1.11.jar" />
<zipfileset src="${gwt.tools.lib}/icu4j/4.4.2/icu4j.jar" />
<zipfileset src="${gwt.tools.lib}/protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar" />
@@ -175,6 +178,7 @@
<!-- htmlunit dependencies not already included: END -->
<zipfileset src="${gwt.tools.lib}/sun/swingworker/swing-worker-1.1.jar" />
<zipfileset src="${gwt.tools.lib}/guava/guava-r06/guava-r06-rebased-2.jar" />
+ <zipfileset src="${gwt.tools.lib}/jscomp/sourcemap-rebased.jar" />
</gwt.jar>
</sequential>
</outofdate>
@@ -216,6 +220,7 @@
<pathelement location="${gwt.tools.lib}/eclipse/jdt-3.4.2.jar" />
<pathelement location="${gwt.tools.lib}/tomcat/commons-collections-3.1.jar" />
<pathelement location="${gwt.tools.lib}/guava/guava-r06/guava-r06-rebased-2.jar" />
+ <pathelement location="${gwt.tools.lib}/jscomp/sourcemap-rebased.jar" />
</classpath>
</gwt.javac>
</target>
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java b/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
index 97cd1d7..4be3a85 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
@@ -92,6 +92,11 @@
String getClassName();
/**
+ * Returns the fragment number in which the symbol is declared (for a method).
+ */
+ int getFragmentNumber();
+
+ /**
* Returns a JSNI-like identifier for the symbol if it a method or field,
* otherwise <code>null</code>.
*/
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 67d2cdf..64fcc55 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
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -27,6 +27,7 @@
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.core.linker.SymbolMapsLinker;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.TextOutput;
import com.google.gwt.dev.util.Util;
@@ -46,20 +47,12 @@
import java.util.TreeMap;
/**
- * A base class for Linkers that use an external script to boostrap the GWT
- * module. This implementation injects JavaScript Snippets into a JS program
- * defined in an external file.
+ * A base class for Linkers that use an external script to boostrap the GWT module. This
+ * implementation injects JavaScript Snippets into a JS program defined in an external file.
*/
public abstract class SelectionScriptLinker extends AbstractLinker {
- /**
- * TODO(bobv): Move this class into c.g.g.core.linker when HostedModeLinker
- * 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";
+ public static final String USE_SOURCE_MAPS_PROPERTY = "compiler.useSourceMaps";
/**
* File name for computeScriptBase.js.
@@ -87,34 +80,45 @@
protected static final String PROCESS_METAS_JS = "com/google/gwt/core/ext/linker/impl/processMetasOld.js";
/**
- * A configuration property that can be used to have the linker load from
- * somewhere other than {@link #FRAGMENT_SUBDIR}.
+ * TODO(bobv): Move this class into c.g.g.core.linker when HostedModeLinker
+ * 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";
+
+ /**
+ * A configuration property that can be used to have the linker load from somewhere other than
+ * {@link #FRAGMENT_SUBDIR}.
*/
private static final String PROP_FRAGMENT_SUBDIR_OVERRIDE = "iframe.linker.deferredjs.subdir";
- protected static void replaceAll(StringBuffer buf, String search,
- String replace) {
- int len = search.length();
- for (int pos = buf.indexOf(search); pos >= 0; pos = buf.indexOf(search,
- pos + 1)) {
- 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.
+ * 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.
+ * @param context
*/
public static String splitPrimaryJavaScript(StatementRanges ranges, String js,
- int charsPerChunk, String scriptChunkSeparator) {
- if (charsPerChunk < 0 || ranges == null) {
+ int charsPerChunk, String scriptChunkSeparator, LinkerContext context) {
+ boolean useSourceMaps = false;
+ for (SelectionProperty prop : context.getProperties()) {
+ if (USE_SOURCE_MAPS_PROPERTY.equals(prop.getName())) {
+ String str = prop.tryGetValue();
+ useSourceMaps = str == null ? false : Boolean.parseBoolean(str);
+ break;
+ }
+ }
+
+ // TODO(cromwellian) enable chunking with sourcemaps
+ if (charsPerChunk < 0 || ranges == null || useSourceMaps) {
return js;
}
@@ -147,13 +151,22 @@
return sb.toString();
}
+ protected static void replaceAll(StringBuffer buf, String search,
+ String replace) {
+ int len = search.length();
+ for (int pos = buf.indexOf(search); pos >= 0; pos = buf.indexOf(search,
+ pos + 1)) {
+ buf.replace(pos, pos + len, replace);
+ }
+ }
+
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.
+ * This method is left in place for existing subclasses of SelectionScriptLinker that have not
+ * been upgraded for the sharding API.
*/
@Override
public ArtifactSet link(TreeLogger logger, LinkerContext context,
@@ -169,15 +182,26 @@
throws UnableToCompleteException {
if (onePermutation) {
ArtifactSet toReturn = new ArtifactSet(artifacts);
+ ArtifactSet writableArtifacts = new ArtifactSet(artifacts);
/*
* Support having multiple compilation results because this method is also
* called from the legacy link method.
*/
for (CompilationResult compilation : toReturn.find(CompilationResult.class)) {
- toReturn.addAll(doEmitCompilation(logger, context, compilation, artifacts));
+ // pass a writable set so that other stages can use this set for temporary storage
+ toReturn.addAll(doEmitCompilation(logger, context, compilation, writableArtifacts));
maybeAddHostedModeFile(logger, context, toReturn, compilation);
}
+ /*
+ * Find edit artifacts added during linking and add them. This is done as a way of returning
+ * arbitrary extra outputs from within the linker methods without having to modify
+ * their return signatures to pass extra return data around.
+ */
+ for (SymbolMapsLinker.ScriptFragmentEditsArtifact ea : writableArtifacts.find(
+ SymbolMapsLinker.ScriptFragmentEditsArtifact.class)) {
+ toReturn.add(ea);
+ }
return toReturn;
} else {
permutationsUtil.setupPermutationsMap(artifacts);
@@ -198,8 +222,8 @@
}
/**
- * Extract via {@link #CHUNK_SIZE_PROPERTY} the number of characters to be
- * included in each chunk.
+ * 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();
@@ -223,9 +247,10 @@
String[] js = result.getJavaScript();
byte[][] bytes = new byte[js.length][];
bytes[0] = generatePrimaryFragment(logger, context, result, js, artifacts);
+
for (int i = 1; i < js.length; i++) {
- bytes[i] = Util.getBytes(generateDeferredFragment(logger, context, i,
- js[i]));
+ bytes[i] = Util.getBytes(generateDeferredFragment(logger, context, i, js[i], artifacts,
+ result));
}
Collection<Artifact<?>> toReturn = new ArrayList<Artifact<?>>();
@@ -237,7 +262,6 @@
}
toReturn.addAll(emitSelectionInformation(result.getStrongName(), result));
-
return toReturn;
}
@@ -260,10 +284,10 @@
return emitString(logger, ss, context.getModuleName()
+ ".nocache.js", lastModified);
}
-
+
/**
- * Generate a selection script. The selection information should previously
- * have been scanned using {@link PermutationsUtil#setupPermutationsMap(ArtifactSet)}.
+ * Generate a selection script. The selection information should previously have been scanned
+ * using {@link PermutationsUtil#setupPermutationsMap(ArtifactSet)}.
*/
protected String fillSelectionScriptTemplate(StringBuffer selectionScript,
TreeLogger logger, LinkerContext context, ArtifactSet artifacts,
@@ -281,10 +305,10 @@
}
replaceAll(selectionScript, "__COMPUTE_SCRIPT_BASE__", computeScriptBase);
replaceAll(selectionScript, "__PROCESS_METAS__", processMetas);
-
+
selectionScript = ResourceInjectionUtil.injectResources(selectionScript, artifacts);
permutationsUtil.addPermutationsJs(selectionScript, logger, context);
-
+
replaceAll(selectionScript, "__MODULE_FUNC__",
context.getModuleFunctionName());
replaceAll(selectionScript, "__MODULE_NAME__", context.getModuleName());
@@ -292,71 +316,98 @@
return selectionScript.toString();
}
-
+
/**
- * @param logger a TreeLogger
- * @param context a LinkerContext
+ * @param logger a TreeLogger
+ * @param context a LinkerContext
* @param fragment the fragment number
*/
protected String generateDeferredFragment(TreeLogger logger,
- LinkerContext context, int fragment, String js) {
- return js;
+ LinkerContext context, int fragment, String js, ArtifactSet artifacts,
+ CompilationResult result)
+ throws UnableToCompleteException {
+ StringBuilder b = new StringBuilder();
+ String strongName = result == null ? "" : result.getStrongName();
+ String prefix = getDeferredFragmentPrefix(logger, context, fragment);
+ b.append(prefix);
+ b.append(js);
+ b.append(getDeferredFragmentSuffix(logger, context, fragment));
+ SymbolMapsLinker.ScriptFragmentEditsArtifact editsArtifact
+ = new SymbolMapsLinker.ScriptFragmentEditsArtifact(strongName, fragment);
+ editsArtifact.prefixLines(prefix);
+ artifacts.add(editsArtifact);
+ return wrapDeferredFragment(logger, context, fragment, b.toString(), artifacts);
}
/**
- * Generate the primary fragment. The default implementation is based on
- * {@link #getModulePrefix(TreeLogger, LinkerContext, String, int)} and
- * {@link #getModuleSuffix(TreeLogger, LinkerContext)}.
+ * 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,
ArtifactSet artifacts) throws UnableToCompleteException {
TextOutput to = new DefaultTextOutput(context.isOutputCompact());
String temp = splitPrimaryJavaScript(result.getStatementRanges()[0], js[0],
- charsPerChunk(context, logger), getScriptChunkSeparator(logger, context));
+ charsPerChunk(context, logger), getScriptChunkSeparator(logger, context), context);
to.print(generatePrimaryFragmentString(
logger, context, result, temp, js.length, artifacts));
return Util.getBytes(to.toString());
}
-
+
protected String generatePrimaryFragmentString(TreeLogger logger,
LinkerContext context, CompilationResult result, String js, int length,
- ArtifactSet artifacts)
- throws UnableToCompleteException {
+ ArtifactSet artifacts)
+ throws UnableToCompleteException {
StringBuffer b = new StringBuffer();
String strongName = result == null ? "" : result.getStrongName();
- b.append(getModulePrefix(logger, context, strongName, length));
+
+ String modulePrefix = getModulePrefix(logger, context, strongName, length);
+ SymbolMapsLinker.ScriptFragmentEditsArtifact editsArtifact
+ = new SymbolMapsLinker.ScriptFragmentEditsArtifact(strongName, 0);
+ editsArtifact.prefixLines(modulePrefix);
+ artifacts.add(editsArtifact);
+ b.append(modulePrefix);
b.append(js);
b.append(getModuleSuffix(logger, context));
return wrapPrimaryFragment(logger, context, b.toString(), artifacts, result);
}
-
+
protected String generateSelectionScript(TreeLogger logger,
LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException {
return generateSelectionScript(logger, context, artifacts, null);
}
-
+
protected String generateSelectionScript(TreeLogger logger,
- LinkerContext context, ArtifactSet artifacts, CompilationResult result)
- throws UnableToCompleteException {
+ LinkerContext context, ArtifactSet artifacts, CompilationResult result)
+ throws UnableToCompleteException {
String selectionScriptText;
StringBuffer buffer = readFileToStringBuffer(
- getSelectionScriptTemplate(logger,context), logger);
+ getSelectionScriptTemplate(logger, context), logger);
selectionScriptText = fillSelectionScriptTemplate(
buffer, logger, context, artifacts, result);
selectionScriptText =
- context.optimizeJavaScript(logger, selectionScriptText);
+ context.optimizeJavaScript(logger, selectionScriptText);
return selectionScriptText;
}
-
+
protected abstract String getCompilationExtension(TreeLogger logger,
LinkerContext context) throws UnableToCompleteException;
+ protected String getDeferredFragmentPrefix(TreeLogger logger, LinkerContext context,
+ int fragment) {
+ return "";
+ }
+
+ protected String getDeferredFragmentSuffix(TreeLogger logger, LinkerContext context,
+ int fragment) {
+ return "";
+ }
+
/**
- * Returns the subdirectory name to be used by getModulPrefix when requesting
- * a runAsync module. It is specified by
- * {@link #PROP_FRAGMENT_SUBDIR_OVERRIDE} and, aside from test cases, is
- * always {@link #FRAGMENT_SUBDIR}.
+ * Returns the subdirectory name to be used by getModulPrefix when requesting a runAsync module.
+ * It is specified by {@link #PROP_FRAGMENT_SUBDIR_OVERRIDE} and, aside from test cases, is always
+ * {@link #FRAGMENT_SUBDIR}.
*/
protected final String getFragmentSubdir(TreeLogger logger,
LinkerContext context) throws UnableToCompleteException {
@@ -375,27 +426,25 @@
return subdir;
}
-
+
protected String getHostedFilename() {
return "";
}
-
+
/**
- * Compute the beginning of a JavaScript file that will hold the main module
- * implementation.
+ * Compute the beginning of a JavaScript file that will hold the main module implementation.
*/
protected abstract String getModulePrefix(TreeLogger logger,
LinkerContext context, String strongName)
throws UnableToCompleteException;
-
+
/**
- * Compute the beginning of a JavaScript file that will hold the main module
- * implementation. By default, calls
- * {@link #getModulePrefix(TreeLogger, LinkerContext, String)}.
- *
- * @param strongName strong name of the module being emitted
- * @param numFragments the number of fragments for this module, including the
- * main fragment (fragment 0)
+ * Compute the beginning of a JavaScript file that will hold the main module implementation. By
+ * default, calls {@link #getModulePrefix(TreeLogger, LinkerContext, String)}.
+ *
+ * @param strongName strong name of the module being emitted
+ * @param numFragments the number of fragments for this module, including the main fragment
+ * (fragment 0)
*/
protected String getModulePrefix(TreeLogger logger, LinkerContext context,
String strongName, int numFragments) throws UnableToCompleteException {
@@ -406,9 +455,9 @@
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.
+ * 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 "";
@@ -416,11 +465,11 @@
protected abstract String getSelectionScriptTemplate(TreeLogger logger,
LinkerContext context) throws UnableToCompleteException;
-
+
/**
* Add the Development Mode file to the artifact set.
*/
- protected void maybeAddHostedModeFile(TreeLogger logger,
+ protected void maybeAddHostedModeFile(TreeLogger logger,
LinkerContext context, ArtifactSet artifacts, CompilationResult result)
throws UnableToCompleteException {
String hostedFilename = getHostedFilename();
@@ -479,6 +528,12 @@
return buffer;
}
+ protected String wrapDeferredFragment(TreeLogger logger,
+ LinkerContext context, int fragment, String script, ArtifactSet artifacts)
+ throws UnableToCompleteException {
+ return script;
+ }
+
protected String wrapPrimaryFragment(TreeLogger logger,
LinkerContext context, String script, ArtifactSet artifacts,
CompilationResult result) throws UnableToCompleteException {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
index 78cfb0c..d1344a6 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
@@ -57,6 +57,7 @@
}
private String className;
+ private int fragmentNumber = -1;
private String memberName;
private String methodSig;
private int sourceLine;
@@ -91,6 +92,10 @@
return className;
}
+ public int getFragmentNumber() {
+ return fragmentNumber;
+ }
+
public String getJsniIdent() {
if (memberName == null) {
return null;
@@ -137,6 +142,10 @@
return methodSig != null;
}
+ public void setFragmentNumber(int fragNum) {
+ fragmentNumber = fragNum;
+ }
+
public void setSymbolName(String symbolName) {
this.symbolName = symbolName;
}
@@ -168,6 +177,7 @@
queryId = in.readInt();
castableTypeMap = (CastableTypeMap) in.readObject();
seedId = in.readInt();
+ fragmentNumber = in.readInt();
}
/**
@@ -197,5 +207,7 @@
out.writeInt(queryId);
out.writeObject(castableTypeMap);
out.writeInt(seedId);
+ out.writeInt(fragmentNumber);
}
+
}
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 2417267..d7e56f0 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
@@ -8,21 +8,48 @@
__WAIT_FOR_BODY_LOADED__
function installCode(code) {
- var docbody = getInstallLocation();
- for (var i = 0; i < code.length; i++) {
- var script = getInstallLocationDoc().createElement('script');
- script.language='javascript';
- script.text = code[i];
- docbody.appendChild(script);
-
+ function removeScript(body, element) {
// 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);
+ body.removeChild(element);
__END_OBFUSCATED_ONLY__
}
+
+ var docbody = getInstallLocation();
+ var doc = getInstallLocationDoc();
+ var script;
+ // for sourcemaps, we inject textNodes into the script element on Chrome
+ if (navigator.userAgent.indexOf("Chrome") > -1 && window.JSON) {
+ var scriptFrag = doc.createDocumentFragment()
+ // surround code with eval until crbug #90707
+ scriptFrag.appendChild(doc.createTextNode("eval(\""));
+ for (var i = 0; i < code.length; i++) {
+ // escape newlines, backslashes, and quotes with JSON.stringify
+ // rather than create multiple script tags which mess up line numbers, we use 1 tag, multiple text nodes
+ var c = window.JSON.stringify(code[i]);
+ // trim beginning/end quotes
+ scriptFrag.appendChild(doc.createTextNode(c.substring(1, c.length - 1)));
+ }
+ // close the eval
+ scriptFrag.appendChild(doc.createTextNode("\");"));
+ script = doc.createElement('script');
+ script.language='javascript';
+ script.appendChild(scriptFrag);
+ docbody.appendChild(script);
+ removeScript(docbody, script);
+ } else {
+ for (var i = 0; i < code.length; i++) {
+ script = doc.createElement('script');
+ script.language='javascript';
+ script.text = code[i];
+ docbody.appendChild(script);
+ removeScript(docbody, script);
+ }
+ }
}
-
+
// Set up a script tag to start downloading immediately, as well as a
// callback to install the code once it is downloaded and the body is loaded.
__MODULE_FUNC__.onScriptDownloaded = function(code) {
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 9375003..52ad608 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
@@ -7,21 +7,48 @@
__WAIT_FOR_BODY_LOADED__
function installCode(code) {
- var docbody = getInstallLocation();
- for (var i = 0; i < code.length; i++) {
- var script = getInstallLocationDoc().createElement('script');
- script.language='javascript';
- script.text = code[i];
- docbody.appendChild(script);
-
+ function removeScript(body, element) {
// 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);
+ body.removeChild(element);
__END_OBFUSCATED_ONLY__
}
+
+ var docbody = getInstallLocation();
+ var doc = getInstallLocationDoc();
+ var script;
+ // for sourcemaps, we inject textNodes into the script element on Chrome
+ if (navigator.userAgent.indexOf("Chrome") > -1 && window.JSON) {
+ var scriptFrag = doc.createDocumentFragment()
+ // surround code with eval until crbug #90707
+ scriptFrag.appendChild(doc.createTextNode("eval(\""));
+ for (var i = 0; i < code.length; i++) {
+ // escape newlines, backslashes, and quotes with JSON.stringify
+ // rather than create multiple script tags which mess up line numbers, we use 1 tag, multiple text nodes
+ var c = window.JSON.stringify(code[i]);
+ // trim beginning/end quotes
+ scriptFrag.appendChild(doc.createTextNode(c.substring(1, c.length - 1)));
+ }
+ // close the eval
+ scriptFrag.appendChild(doc.createTextNode("\");"));
+ script = doc.createElement('script');
+ script.language='javascript';
+ script.appendChild(scriptFrag);
+ docbody.appendChild(script);
+ removeScript(docbody, script);
+ } else {
+ for (var i = 0; i < code.length; i++) {
+ script = doc.createElement('script');
+ script.language='javascript';
+ script.text = code[i];
+ docbody.appendChild(script);
+ removeScript(docbody, script);
+ }
+ }
}
-
+
// Set up a script tag to start downloading immediately, as well as a
// callback to install the code once it is downloaded and the body is loaded.
__MODULE_FUNC__.onScriptDownloaded = function(code) {
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/Range.java b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
index f98fe68..9239310 100644
--- a/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
@@ -55,7 +55,21 @@
};
final int end;
+ final int endLine;
+ final int endColumn;
final int start;
+ final int startLine;
+ final int startColumn;
+
+ /**
+ * Constructor.
+ *
+ * @param start must be non-negative
+ * @param end must be greater than or equal to <code>start</code>
+ */
+ public Range(int start, int end) {
+ this(start, end, 0, 0, 0, 0);
+ }
/**
* Constructor.
@@ -63,11 +77,17 @@
* @param start must be non-negative
* @param end must be greater than or equal to <code>start</code>
*/
- public Range(int start, int end) {
+ public Range(int start, int end, int startLine, int startColumn, int endLine, int endColumn) {
+
assert start >= 0;
assert start <= end;
+
this.start = start;
this.end = end;
+ this.startLine = startLine;
+ this.startColumn = startColumn;
+ this.endLine = endLine;
+ this.endColumn = endColumn;
}
/**
@@ -90,10 +110,26 @@
return end;
}
+ public int getEndColumn() {
+ return endColumn;
+ }
+
+ public int getEndLine() {
+ return endLine;
+ }
+
public int getStart() {
return start;
}
+ public int getStartColumn() {
+ return startColumn;
+ }
+
+ public int getStartLine() {
+ return startLine;
+ }
+
@Override
public int hashCode() {
return 37 * start + end;
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/SourceMapRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/SourceMapRecorder.java
new file mode 100644
index 0000000..8bd5bbf
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/SourceMapRecorder.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.soyc;
+
+import com.google.gwt.thirdparty.debugging.sourcemap.FilePosition;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapFormat;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGenerator;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGeneratorFactory;
+import com.google.gwt.core.ext.linker.SyntheticArtifact;
+import com.google.gwt.core.linker.SymbolMapsLinker;
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.SourceInfo;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Creates Closure Compatible SourceMaps.
+ */
+public class SourceMapRecorder {
+
+ public static List<SyntheticArtifact> makeSourceMapArtifacts(
+ List<Map<Range, SourceInfo>> sourceInfoMaps,
+ int permutationId) {
+ List<SyntheticArtifact> toReturn = new ArrayList<SyntheticArtifact>();
+ recordSourceMap(sourceInfoMaps, toReturn, permutationId);
+ return toReturn;
+ }
+
+ public static void recordSourceMap(List<Map<Range, SourceInfo>> sourceInfoMaps,
+ List<SyntheticArtifact> artifacts, int permutationId) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ SourceMapGenerator generator = SourceMapGeneratorFactory.getInstance(SourceMapFormat.V3);
+ OutputStreamWriter out = new OutputStreamWriter(baos);
+ int fragment = 0;
+ if (!sourceInfoMaps.isEmpty()) {
+ for (Map<Range, SourceInfo> sourceMap : sourceInfoMaps) {
+ generator.reset();
+ Set<Range> rangeSet = sourceMap.keySet();
+ Range[] ranges = rangeSet.toArray(new Range[rangeSet.size()]);
+ Arrays.sort(ranges, Range.DEPENDENCY_ORDER_COMPARATOR);
+ for (Range r : ranges) {
+ SourceInfo si = sourceMap.get(r);
+ if (si.getFileName() == null || si.getStartLine() < 0) {
+ // skip over synthetics with no Java source
+ continue;
+ }
+ if (r.getStartLine() == 0 || r.getEndLine() == 0) {
+ // or other bogus entries that appear
+ continue;
+ }
+ generator.addMapping(si.getFileName(), null,
+ new FilePosition(si.getStartLine(), 0),
+ new FilePosition(r.getStartLine(), r.getStartColumn()),
+ new FilePosition(r.getEndLine(), r.getEndColumn()));
+ }
+ try {
+ baos.reset();
+ generator.appendTo(out, "sourceMap" + fragment);
+ out.flush();
+ artifacts.add(new SymbolMapsLinker.SourceMapArtifact(permutationId, fragment,
+ baos.toByteArray()));
+ fragment++;
+ } catch (IOException e) {
+ throw new InternalCompilerException(e.toString(), e);
+ }
+ }
+ }
+ }
+}
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 4635518..1c74cf1 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -61,22 +61,6 @@
private static final String FAIL_IF_SCRIPT_TAG_PROPERTY = "xsiframe.failIfScriptTag";
@Override
- protected String generateDeferredFragment(TreeLogger logger,
- LinkerContext context, int fragment, String js) {
- // TODO(unnurg): This assumes that the xsiframe linker is using the
- // ScriptTagLoadingStrategy (since it is also xs compatible). However,
- // it should be completely valid to use the XhrLoadingStrategy with this
- // linker, in which case we would not want to wrap the deferred fragment
- // in this way. Ideally, we should make a way for this code to be dependent
- // on what strategy is being used. Otherwise, we should make a property which
- // users can set to turn this wrapping off if they override the loading strategy.
- return String.format("$wnd.%s.runAsyncCallback%d(%s)\n",
- context.getModuleFunctionName(),
- fragment,
- JsToStringGenerationVisitor.javaScriptString(js));
- }
-
- @Override
public String getDescription() {
return "Cross-Site-Iframe";
}
@@ -103,7 +87,7 @@
// Must do permutations before providers
includeJs(ss, logger, getJsPermutations(context), "__PERMUTATIONS__");
includeJs(ss, logger, getJsProperties(context), "__PROPERTIES__");
-
+
// Order doesn't matter for the rest
includeJs(ss, logger, getJsProcessMetas(context), "__PROCESS_METAS__");
includeJs(ss, logger, getJsInstallLocation(context), "__INSTALL_LOCATION__");
@@ -189,6 +173,11 @@
return ".cache.js";
}
+ protected String getDeferredFragmentSuffix(TreeLogger logger, LinkerContext context,
+ int fragment) {
+ return "\n//@ sourceURL=" + fragment + ".js\n";
+ }
+
@Override
protected String getHostedFilename() {
return "devmode.js";
@@ -303,7 +292,7 @@
protected String getJsProperties(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/properties.js";
}
-
+
/**
* Returns the name of the {@code JsRunAsync} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/runAsync.js"}.
@@ -362,7 +351,7 @@
out.newlineOpt();
out.print("function __gwtInstallCode(code) {return __gwtModuleFunction.__installRunAsyncCode(code);}");
out.newlineOpt();
-
+
// Even though we call the $sendStats function in the code written in this
// linker, some of the compilation code still needs the $stats and
// $sessionId
@@ -386,7 +375,7 @@
+ "__gwtModuleFunction.__computePropValue);");
out.newlineOpt();
out.print("$sendStats('moduleStartup', 'end');");
-
+ out.print("\n//@ sourceURL=0.js\n");
return out.toString();
}
@@ -506,6 +495,22 @@
}
@Override
+ protected String wrapDeferredFragment(TreeLogger logger,
+ LinkerContext context, int fragment, String js, ArtifactSet artifacts) {
+ // TODO(unnurg): This assumes that the xsiframe linker is using the
+ // ScriptTagLoadingStrategy (since it is also xs compatible). However,
+ // it should be completely valid to use the XhrLoadingStrategy with this
+ // linker, in which case we would not want to wrap the deferred fragment
+ // in this way. Ideally, we should make a way for this code to be dependent
+ // on what strategy is being used. Otherwise, we should make a property which
+ // users can set to turn this wrapping off if they override the loading strategy.
+ return String.format("$wnd.%s.runAsyncCallback%d(%s)\n",
+ context.getModuleFunctionName(),
+ fragment,
+ JsToStringGenerationVisitor.javaScriptString(js));
+ }
+
+ @Override
protected String wrapPrimaryFragment(TreeLogger logger, LinkerContext context, String script,
ArtifactSet artifacts, CompilationResult result) throws UnableToCompleteException {
StringBuffer out = new StringBuffer();
@@ -529,5 +534,4 @@
}
return out.toString();
}
-
}
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 ded6042..fae5c92 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -18,15 +18,12 @@
import com.google.gwt.core.ext.LinkerContext;
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.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.impl.SelectionScriptLinker;
import com.google.gwt.dev.About;
import com.google.gwt.dev.util.DefaultTextOutput;
-import com.google.gwt.dev.util.Util;
/**
* Implements the canonical GWT bootstrap sequence that loads the GWT module in
@@ -46,26 +43,6 @@
return "Standard";
}
- /*
- * 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,
- ArtifactSet artifacts) 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), getScriptChunkSeparator(logger, context)));
- b.append(getModuleSuffix(logger, context));
- return Util.getBytes(b.toString());
- }
-
@Override
protected String getCompilationExtension(TreeLogger logger,
LinkerContext context) {
@@ -118,7 +95,7 @@
protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context) {
return "com/google/gwt/core/linker/IFrameTemplate.js";
}
-
+
protected String modifyPrimaryJavaScript(String js) {
return js;
}
diff --git a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
index 5dc937e..260ed9e 100644
--- a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,39 +15,176 @@
*/
package com.google.gwt.core.linker;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGeneratorV3;
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
+import com.google.gwt.core.ext.linker.Artifact;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.LinkerOrder.Order;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.core.ext.linker.Shardable;
import com.google.gwt.core.ext.linker.SymbolData;
-import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.ext.linker.SyntheticArtifact;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.collect.HashMap;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.SortedMap;
/**
- * This Linker exports the symbol maps associated with each compilation result
- * as a private file. The names of the symbol maps files are computed by
- * appending {@value #STRONG_NAME_SUFFIX} to the value returned by
- * {@link CompilationResult#getStrongName()}.
+ * This Linker exports the symbol maps associated with each compilation result as a private file.
+ * The names of the symbol maps files are computed by appending {@value #STRONG_NAME_SUFFIX} to the
+ * value returned by {@link CompilationResult#getStrongName()}.
*/
@LinkerOrder(Order.POST)
@Shardable
public class SymbolMapsLinker extends AbstractLinker {
+
/**
- * This value is appended to the strong name of the CompilationResult to form
- * the symbol map's filename.
+ * Artifact to record insertions or deletions made to Javascript fragments.
+ */
+ public static class ScriptFragmentEditsArtifact extends Artifact<ScriptFragmentEditsArtifact> {
+
+ /**
+ * Operation type performed on script.
+ */
+ public enum Edit {
+ PREFIX, INSERT, REMOVE;
+ }
+
+ private static class EditOperation {
+
+ public static EditOperation insert(int lineNumber, String data) {
+ return new EditOperation(Edit.INSERT, lineNumber, data);
+ }
+
+ public static EditOperation prefix(String data) {
+ return new EditOperation(Edit.PREFIX, 0, data);
+ }
+
+ public static EditOperation remove(int lineNumber) {
+ return new EditOperation(Edit.REMOVE, lineNumber, null);
+ }
+
+ Edit op;
+ int lineNumber;
+ int numLines;
+
+ public EditOperation(
+ Edit op, int lineNumber, String data) {
+ this.op = op;
+ this.lineNumber = lineNumber;
+ this.numLines = countNewLines(data);
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ public int getNumLines() {
+ return numLines;
+ }
+
+ public Edit getOp() {
+ return op;
+ }
+
+ private int countNewLines(String chunkJs) {
+ int newLineCount = 0;
+ for (int j = 0; j < chunkJs.length(); j++) {
+ if (chunkJs.charAt(j) == '\n') {
+ newLineCount++;
+ }
+ }
+ return newLineCount;
+ }
+ }
+
+ private List<EditOperation> editOperations = new ArrayList<EditOperation>();
+
+ private String strongName;
+ private int fragment;
+
+ public ScriptFragmentEditsArtifact(String strongName,
+ int fragment) {
+ super(SymbolMapsLinker.class);
+ this.strongName = strongName;
+ this.fragment = fragment;
+ }
+
+ public int getFragment() {
+ return fragment;
+ }
+
+ public String getStrongName() {
+ return strongName;
+ }
+
+ @Override
+ public int hashCode() {
+ return (strongName + fragment).hashCode();
+ }
+
+ public void insertLinesBefore(int position, String lines) {
+ editOperations.add(EditOperation.insert(position, lines));
+ }
+
+ public void prefixLines(String lines) {
+ editOperations.add(EditOperation.prefix(lines));
+ }
+
+ @Override
+ protected int compareToComparableArtifact(SymbolMapsLinker.ScriptFragmentEditsArtifact o) {
+ int result = (strongName + fragment).compareTo(strongName + fragment);
+ return result;
+ }
+
+ @Override
+ protected Class<ScriptFragmentEditsArtifact> getComparableArtifactType() {
+ return ScriptFragmentEditsArtifact.class;
+ }
+ }
+
+ /**
+ * Artifact to represent a sourcemap file to be processed by SymbolMapsLinker.
+ */
+ public static class SourceMapArtifact extends SyntheticArtifact {
+
+ private int permutationId;
+ private int fragment;
+ private byte[] js;
+
+ public SourceMapArtifact(int permutationId, int fragment, byte[] js) {
+ super(SymbolMapsLinker.class, permutationId + "/sourceMap" + fragment + ".json", js);
+ this.permutationId = permutationId;
+ this.fragment = fragment;
+ this.js = js;
+ }
+
+ public int getFragment() {
+ return fragment;
+ }
+
+ public int getPermutationId() {
+ return permutationId;
+ }
+ }
+
+ /**
+ * This value is appended to the strong name of the CompilationResult to form the symbol map's
+ * filename.
*/
public static final String STRONG_NAME_SUFFIX = ".symbolMap";
@@ -98,17 +235,59 @@
throws UnableToCompleteException {
if (onePermutation) {
artifacts = new ArtifactSet(artifacts);
-
+ Map<Integer, String> permMap = new HashMap<Integer, String>();
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (CompilationResult result : artifacts.find(CompilationResult.class)) {
PrintWriter pw = new PrintWriter(out);
-
+ permMap.put(result.getPermutationId(), result.getStrongName());
doWriteSymbolMap(logger, result, pw);
pw.close();
doEmitSymbolMap(logger, artifacts, result, out);
out.reset();
}
+
+ for (SourceMapArtifact se : artifacts.find(SourceMapArtifact.class)) {
+ // filename is permutation_id/sourceMap<fragmentNumber>.json
+ String sourceMapString = Util.readStreamAsString(se.getContents(logger));
+ String strongName = permMap.get(se.getPermutationId());
+ String partialPath = strongName + "_sourceMap" + se.getFragment() + ".json";
+
+ int fragment = se.getFragment();
+ ScriptFragmentEditsArtifact editArtifact = null;
+ for (ScriptFragmentEditsArtifact mp : artifacts.find(ScriptFragmentEditsArtifact.class)) {
+ if (mp.getStrongName().equals(strongName) && mp.getFragment() == fragment) {
+ editArtifact = mp;
+ artifacts.remove(editArtifact);
+ break;
+ }
+ }
+
+ SyntheticArtifact emArt = null;
+ // no need to adjust source map
+ if (editArtifact == null) {
+ emArt = emitSourceMapString(logger, sourceMapString, partialPath);
+ } else {
+ SourceMapGeneratorV3 sourceMapGenerator = new SourceMapGeneratorV3();
+ try {
+ int totalPrefixLines = 0;
+ for (ScriptFragmentEditsArtifact.EditOperation op : editArtifact.editOperations) {
+ if (op.getOp() == ScriptFragmentEditsArtifact.Edit.PREFIX) {
+ totalPrefixLines += op.getNumLines();
+ }
+ }
+ // TODO(cromwellian): apply insert and remove edits
+ sourceMapGenerator.mergeMapSection(totalPrefixLines, 0, sourceMapString);
+ StringWriter stringWriter = new StringWriter();
+ sourceMapGenerator.appendTo(stringWriter, "sourceMap");
+ emArt = emitSourceMapString(logger, stringWriter.toString(), partialPath);
+ } catch (Exception e) {
+ logger.log(TreeLogger.Type.WARN, "Can't write source map " + partialPath, e);
+ }
+ }
+ artifacts.add(emArt);
+ artifacts.remove(se);
+ }
}
return artifacts;
}
@@ -128,11 +307,10 @@
/**
* Override to change the format of the symbol map.
- *
+ *
* @param logger the logger to write to
* @param result the compilation result
- * @param pw the output PrintWriter
- *
+ * @param pw the output PrintWriter
* @throws UnableToCompleteException if an error occurs
*/
protected void doWriteSymbolMap(TreeLogger logger, CompilationResult result,
@@ -145,7 +323,7 @@
pw.println(" }");
}
- pw.println("# jsName, jsniIdent, className, memberName, sourceUri, sourceLine");
+ pw.println("# jsName, jsniIdent, className, memberName, sourceUri, sourceLine, fragmentNumber");
StringBuilder sb = new StringBuilder(1024);
char[] buf = new char[1024];
for (SymbolData symbol : result.getSymbolMap()) {
@@ -170,6 +348,8 @@
}
sb.append(',');
sb.append(symbol.getSourceLine());
+ sb.append(',');
+ sb.append(symbol.getFragmentNumber());
sb.append('\n');
int sbLen = sb.length();
@@ -185,4 +365,11 @@
sb.setLength(0);
}
}
+
+ protected SyntheticArtifact emitSourceMapString(TreeLogger logger, String contents,
+ String partialPath) throws UnableToCompleteException {
+ SyntheticArtifact emArt = emitString(logger, contents, partialPath);
+ emArt.setVisibility(Visibility.LegacyDeploy);
+ return emArt;
+ }
}
diff --git a/dev/core/src/com/google/gwt/core/linker/XSLinker.java b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
index 1ffc1f6..b6b2641 100644
--- a/dev/core/src/com/google/gwt/core/linker/XSLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -17,9 +17,10 @@
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.LinkerOrder;
-import com.google.gwt.core.ext.linker.Shardable;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.ext.linker.Shardable;
import com.google.gwt.core.ext.linker.impl.SelectionScriptLinker;
import com.google.gwt.dev.About;
import com.google.gwt.dev.js.JsToStringGenerationVisitor;
@@ -37,19 +38,6 @@
}
@Override
- protected String generateDeferredFragment(TreeLogger logger,
- LinkerContext context, int fragment, String js) {
- StringBuilder sb = new StringBuilder();
- sb.append(context.getModuleFunctionName());
- sb.append(".runAsyncCallback");
- sb.append(fragment);
- sb.append("(");
- sb.append(JsToStringGenerationVisitor.javaScriptString(js));
- sb.append(");\n");
- return sb.toString();
- }
-
- @Override
protected String getCompilationExtension(TreeLogger logger,
LinkerContext context) {
return ".cache.js";
@@ -94,6 +82,15 @@
return "com/google/gwt/core/linker/XSTemplate.js";
}
+ @Override
+ protected String wrapDeferredFragment(TreeLogger logger,
+ LinkerContext context, int fragment, String js, ArtifactSet artifacts) {
+ return String.format("%s.runAsyncCallback%d(%s)\n",
+ context.getModuleFunctionName(),
+ fragment,
+ JsToStringGenerationVisitor.javaScriptString(js));
+ }
+
private String getModulePrefix(LinkerContext context, String strongName,
boolean supportRunAsync) {
DefaultTextOutput out = new DefaultTextOutput(context.isOutputCompact());
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 db7f4f7..c0bf87c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -32,6 +32,7 @@
import com.google.gwt.core.ext.linker.SyntheticArtifact;
import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.core.ext.soyc.SourceMapRecorder;
import com.google.gwt.core.ext.soyc.impl.DependencyRecorder;
import com.google.gwt.core.ext.soyc.impl.SizeMapRecorder;
import com.google.gwt.core.ext.soyc.impl.SplitPointRecorder;
@@ -120,8 +121,11 @@
import com.google.gwt.dev.js.JsVerboseNamer;
import com.google.gwt.dev.js.SizeBreakdown;
import com.google.gwt.dev.js.ast.JsBlock;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.Empty;
import com.google.gwt.dev.util.Memory;
@@ -144,6 +148,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -351,21 +356,11 @@
}
// detect if browser is ie6 or not known
- boolean isIE6orUnknown = false;
- for (PropertyOracle oracle : propertyOracles) {
- try {
- SelectionProperty userAgentProperty = oracle.getSelectionProperty(logger, "user.agent");
- if ("ie6".equals(userAgentProperty.getCurrentValue())) {
- isIE6orUnknown = true;
- break;
- }
- } catch (BadPropertyValueException e) {
- // user agent unknown; play it safe
- isIE6orUnknown = true;
- break;
- }
- }
+ boolean isIE6orUnknown = findBooleanProperty(propertyOracles, logger, "user.agent", "ie6",
+ true, false, true);
+ boolean isSourceMapsEnabled = findBooleanProperty(propertyOracles, logger,
+ "compiler.useSourceMaps", "true", true, false, false);
// (10.5) Obfuscate
Map<JsName, String> obfuscateMap = Maps.create();
switch (options.getOutput()) {
@@ -386,7 +381,7 @@
}
break;
case PRETTY:
- // We don't intern strings in pretty mode to improve readability
+ // We don't intern strings in pretty mode to imprmakeSouove readability
JsPrettyNamer.exec(jsProgram);
break;
case DETAILED:
@@ -421,13 +416,12 @@
SizeBreakdown[] sizeBreakdowns =
options.isSoycEnabled() || options.isCompilerMetricsEnabled()
? new SizeBreakdown[js.length] : null;
- List<Map<Range, SourceInfo>> sourceInfoMaps =
- options.isSoycExtra() ? new ArrayList<Map<Range, SourceInfo>>() : null;
+ List<Map<Range, SourceInfo>> sourceInfoMaps = new ArrayList<Map<Range, SourceInfo>>();
generateJavaScriptCode(options, jsProgram, jjsmap, js, ranges, sizeBreakdowns,
- sourceInfoMaps, splitBlocks);
+ sourceInfoMaps, splitBlocks, isSourceMapsEnabled);
PermutationResult toReturn =
- new PermutationResultImpl(js, permutation, makeSymbolMap(symbolTable), ranges);
+ new PermutationResultImpl(js, permutation, makeSymbolMap(symbolTable, jsProgram), ranges);
CompilationMetricsArtifact compilationMetrics = null;
if (options.isCompilerMetricsEnabled()) {
compilationMetrics = new CompilationMetricsArtifact(permutation.getId());
@@ -441,8 +435,15 @@
.getPrecompilationMetrics(), compilationMetrics));
}
toReturn.addArtifacts(makeSoycArtifacts(logger, permutationId, jprogram, js, sizeBreakdowns,
- sourceInfoMaps, dependencies, jjsmap, obfuscateMap, unifiedAst.getModuleMetrics(),
- unifiedAst.getPrecompilationMetrics(), compilationMetrics, options.isSoycHtmlDisabled()));
+ options.isSoycExtra() ? sourceInfoMaps : null, dependencies, jjsmap, obfuscateMap,
+ unifiedAst.getModuleMetrics(), unifiedAst.getPrecompilationMetrics(), compilationMetrics,
+ options.isSoycHtmlDisabled()));
+
+ if (isSourceMapsEnabled) {
+ logger.log(TreeLogger.INFO, "Source Maps Enabled");
+ toReturn.addArtifacts(SourceMapRecorder.makeSourceMapArtifacts(sourceInfoMaps,
+ permutationId));
+ }
logTrackingStats(logger);
if (logger.isLoggable(TreeLogger.TRACE)) {
@@ -457,6 +458,29 @@
}
}
+ /**
+ * Look for a selection property in all property oracles.
+ */
+ public static boolean findBooleanProperty(PropertyOracle[] propertyOracles, TreeLogger logger,
+ String name, String valueToFind, boolean valueIfFound, boolean valueIfNotFound,
+ boolean valueIfError) {
+ boolean toReturn = valueIfNotFound;
+ for (PropertyOracle oracle : propertyOracles) {
+ try {
+ SelectionProperty property = oracle.getSelectionProperty(logger, name);
+ if (valueToFind.equals(property.getCurrentValue())) {
+ toReturn = valueIfFound;
+ break;
+ }
+ } catch (BadPropertyValueException e) {
+ // unknown value play it safe
+ toReturn = valueIfError;
+ break;
+ }
+ }
+ return toReturn;
+ }
+
public static UnifiedAst precompile(TreeLogger logger, ModuleDef module,
RebindPermutationOracle rpo, String[] declEntryPts, String[] additionalRootTypes,
JJSOptions options, boolean singlePermutation) throws UnableToCompleteException {
@@ -936,19 +960,22 @@
* @param options The options this compiler instance is running with
* @param jsProgram The AST to convert to source code
* @param jjsMap A map between the JavaScript AST and the Java AST it came
- * from
+* from
* @param js An array to hold the output JavaScript
* @param ranges An array to hold the statement ranges for that JavaScript
* @param sizeBreakdowns An array to hold the size breakdowns for that
- * JavaScript
+* JavaScript
* @param sourceInfoMaps An array to hold the source info maps for that
- * JavaScript
+* JavaScript
* @param splitBlocks true if current permutation is for IE6 or unknown
+ * @param sourceMapsEnabled
*/
- private static void generateJavaScriptCode(JJSOptions options, JsProgram jsProgram,
+ private static void generateJavaScriptCode(JJSOptions options,
+ JsProgram jsProgram,
JavaToJavaScriptMap jjsMap, String[] js, StatementRanges[] ranges,
- SizeBreakdown[] sizeBreakdowns, List<Map<Range, SourceInfo>> sourceInfoMaps,
- boolean splitBlocks) {
+ SizeBreakdown[] sizeBreakdowns,
+ List<Map<Range, SourceInfo>> sourceInfoMaps,
+ boolean splitBlocks, boolean sourceMapsEnabled) {
for (int i = 0; i < js.length; i++) {
DefaultTextOutput out = new DefaultTextOutput(options.getOutput().shouldMinimize());
JsSourceGenerationVisitorWithSizeBreakdown v;
@@ -980,15 +1007,18 @@
* the top level blocks into sub-blocks if they exceed 32767 statements.
*/
Event functionClusterEvent = SpeedTracerLogger.start(CompilerEventType.FUNCTION_CLUSTER);
- // only cluster for obfuscated mode
- if (options.isAggressivelyOptimize() && options.getOutput() == JsOutputOption.OBFUSCATED) {
+ // TODO(cromwellian) move to the Js AST, re-enable sourcemaps + clustering
+ if (!sourceMapsEnabled
+ && options.isAggressivelyOptimize()
+ // only cluster for obfuscated mode
+ && options.getOutput() == JsOutputOption.OBFUSCATED) {
transformer = new JsFunctionClusterer(transformer);
transformer.exec();
}
functionClusterEvent.end();
// rewrite top-level blocks to limit the number of statements
- if (splitBlocks) {
+ if (!sourceMapsEnabled && splitBlocks) {
transformer = new JsIEBlockTextTransformer(transformer);
transformer.exec();
}
@@ -1155,13 +1185,31 @@
return amp.makeStatement();
}
- private static SymbolData[] makeSymbolMap(Map<StandardSymbolData, JsName> symbolTable) {
+ private static SymbolData[] makeSymbolMap(Map<StandardSymbolData, JsName> symbolTable,
+ JsProgram jsProgram) {
+
+ final Map<JsName, Integer> nameToFragment = new HashMap<JsName, Integer>();
+ for (int i = 0; i < jsProgram.getFragmentCount(); i++) {
+ final Integer fragId = i;
+ new JsVisitor() {
+ @Override
+ public void endVisit(JsFunction x, JsContext ctx) {
+ if (x.getName() != null) {
+ nameToFragment.put(x.getName(), fragId);
+ }
+ }
+ }.accept(jsProgram.getFragmentBlock(i));
+ }
SymbolData[] result = new SymbolData[symbolTable.size()];
int i = 0;
for (Map.Entry<StandardSymbolData, JsName> entry : symbolTable.entrySet()) {
StandardSymbolData symbolData = entry.getKey();
symbolData.setSymbolName(entry.getValue().getShortIdent());
+ Integer fragNum = nameToFragment.get(entry.getValue());
+ if (fragNum != null) {
+ symbolData.setFragmentNumber(fragNum);
+ }
result[i++] = symbolData;
}
return result;
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 0a6f013..d2798ac 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,16 @@
this.textOutput = textOutput;
}
+ @Override
+ public int getColumn() {
+ return textOutput.getColumn();
+ }
+
+ @Override
+ public int getLine() {
+ return textOutput.getLine();
+ }
+
public int getPosition() {
return textOutput.getPosition();
}
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 58812b1..5d4441b 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
@@ -50,10 +50,13 @@
protected <T extends JsVisitable> T doAccept(T node) {
boolean addEntry = node instanceof HasSourceInfo;
int start = addEntry ? out.getPosition() : 0;
+ int sLine = out.getLine();
+ int sCol = out.getColumn();
T toReturn = super.doAccept(node);
if (addEntry) {
SourceInfo info = ((HasSourceInfo) node).getSourceInfo();
- sourceInfoMap.put(new Range(start, out.getPosition()), info);
+ sourceInfoMap.put(new Range(start, out.getPosition(),
+ sLine, sCol, out.getLine(), out.getColumn()), info);
}
return toReturn;
}
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 8e9e8b3..a284d7d 100644
--- a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
+++ b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
@@ -29,11 +29,21 @@
private boolean justNewlined;
private PrintWriter out;
private int position = 0;
+ private int line = 0;
+ private int column = 0;
protected AbstractTextOutput(boolean compact) {
this.compact = compact;
}
+ public int getColumn() {
+ return column;
+ }
+
+ public int getLine() {
+ return line;
+ }
+
public int getPosition() {
return position;
}
@@ -63,6 +73,8 @@
out.print('\n');
}
position++;
+ line++;
+ column = 0;
justNewlined = true;
}
@@ -70,6 +82,8 @@
if (!compact) {
out.print('\n');
position++;
+ line++;
+ column = 0;
justNewlined = true;
}
}
@@ -78,6 +92,7 @@
maybeIndent();
out.print(c);
position++;
+ column++;
justNewlined = false;
}
@@ -98,6 +113,7 @@
maybeIndent();
out.print(c);
position += 1;
+ column++;
}
}
@@ -128,6 +144,7 @@
private void printAndCount(char[] chars) {
position += chars.length;
+ column += 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 ecbf9d0..f98536b 100644
--- a/dev/core/src/com/google/gwt/dev/util/TextOutput.java
+++ b/dev/core/src/com/google/gwt/dev/util/TextOutput.java
@@ -1,12 +1,12 @@
/*
* Copyright 2006 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
@@ -19,6 +19,11 @@
* Interface used for printing text output.
*/
public interface TextOutput {
+
+ int getColumn();
+
+ int getLine();
+
int getPosition();
void indentIn();
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 266935d..f5e0e6b 100644
--- a/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
+++ b/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,6 +15,11 @@
*/
package com.google.gwt.core.linker;
+import com.google.gwt.core.ext.LinkerContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
+import com.google.gwt.core.ext.linker.SelectionProperty;
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;
@@ -22,16 +27,92 @@
import junit.framework.TestCase;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.SortedSet;
+import java.util.TreeSet;
/**
* Tests the script chunking in the {@link SelectionScriptLinker}.
*/
public class ScriptChunkingTest extends TestCase {
+
+ private static class MockLinkerContext implements LinkerContext {
+
+ @Override
+ public SortedSet<ConfigurationProperty> getConfigurationProperties() {
+ return new TreeSet<ConfigurationProperty>();
+ }
+
+ @Override
+ public String getModuleFunctionName() {
+ return "mockFunc";
+ }
+
+ @Override
+ public long getModuleLastModified() {
+ return 0;
+ }
+
+ @Override
+ public String getModuleName() {
+ return "mock";
+ }
+
+ @Override
+ public SortedSet<SelectionProperty> getProperties() {
+ SelectionProperty mockSourceMapProperty = new SelectionProperty() {
+
+ @Override
+ public String getName() {
+ return SelectionScriptLinker.USE_SOURCE_MAPS_PROPERTY;
+ }
+
+ @Override
+ public String getFallbackValue() {
+ return "";
+ }
+
+ @Override
+ public SortedSet<String> getPossibleValues() {
+ return new TreeSet<String>(Arrays.asList("true", "false"));
+ }
+
+ @Override
+ public String getPropertyProvider(TreeLogger logger,
+ SortedSet<ConfigurationProperty> configProperties) throws UnableToCompleteException {
+ return null;
+ }
+
+ @Override
+ public boolean isDerived() {
+ return false;
+ }
+
+ @Override
+ public String tryGetValue() {
+ return "false";
+ }
+ };
+ return new TreeSet<SelectionProperty>(Arrays.asList(mockSourceMapProperty));
+ }
+
+ @Override
+ public boolean isOutputCompact() {
+ return false;
+ }
+
+ @Override
+ public String optimizeJavaScript(TreeLogger logger, String jsProgram)
+ throws UnableToCompleteException {
+ return "";
+ }
+ }
+
/**
- * A class for building up JavaScript that has statements and non-statement
- * code interleaved.
+ * 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>();
@@ -74,12 +155,12 @@
String split = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(),
builder.getJavaScript(), stmt1.length() + stmt2.length(),
- IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
+ IFrameLinker.SCRIPT_CHUNK_SEPARATOR, new MockLinkerContext());
assertEquals(stmt1 + stmt2 + IFrameLinker.SCRIPT_CHUNK_SEPARATOR + stmt3
+ ';' + stmt4, split);
}
-
+
/**
* Test that with the default chunk separator (""), splitting is a no-op.
*/
@@ -99,10 +180,10 @@
builder.addNonStatement("}");
String split = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(),
- builder.getJavaScript(), stmt1.length() + stmt2.length(), "");
+ builder.getJavaScript(), stmt1.length() + stmt2.length(), "", new MockLinkerContext());
assertEquals(stmt1 + stmt2 + stmt3 + ';' + stmt4, split);
}
-
+
/**
* Test a chunk size large enough that no splitting happens.
*/
@@ -122,7 +203,8 @@
builder.addNonStatement("}");
String split = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(),
- builder.getJavaScript(), 10000, IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
+ builder.getJavaScript(), 10000, IFrameLinker.SCRIPT_CHUNK_SEPARATOR,
+ new MockLinkerContext());
assertEquals(stmt1 + stmt2 + stmt3 + ';' + stmt4, split);
}
@@ -141,7 +223,7 @@
builder.addNonStatement("}");
String split = SelectionScriptLinker.splitPrimaryJavaScript(builder.getRanges(),
- builder.getJavaScript(), -1, IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
+ builder.getJavaScript(), -1, IFrameLinker.SCRIPT_CHUNK_SEPARATOR, new MockLinkerContext());
assertEquals(builder.getJavaScript(), split);
}
@@ -160,7 +242,7 @@
builder.addNonStatement("}");
String split = SelectionScriptLinker.splitPrimaryJavaScript(null,
- builder.getJavaScript(), 5, IFrameLinker.SCRIPT_CHUNK_SEPARATOR);
+ builder.getJavaScript(), 5, IFrameLinker.SCRIPT_CHUNK_SEPARATOR, new MockLinkerContext());
assertEquals(builder.getJavaScript(), split);
}
}
diff --git a/servlet/build.xml b/servlet/build.xml
index f274e05..21a57be 100755
--- a/servlet/build.xml
+++ b/servlet/build.xml
@@ -52,6 +52,7 @@
<!-- The following two jars satisfy RequestFactory dependencies. -->
<zipfileset src="${gwt.tools.redist}/json/r2_20080312/json-1.5.jar" />
<zipfileset src="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA.jar" />
+ <zipfileset src="${gwt.tools.lib}/jscomp/sourcemap-rebased.jar" />
</gwt.jar>
</target>
</project>
diff --git a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
index b811b41..a3b11ef 100644
--- a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
+++ b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
@@ -75,4 +75,10 @@
is-multi-valued="false" />
<set-configuration-property name="iframe.linker.deferredjs.subdir"
value="deferredjs" />
+
+ <!--
+ Specifies whether a given permutation has source map support enabled.
+ -->
+ <define-property name="compiler.useSourceMaps" values="true,false"/>
+ <set-property name="compiler.useSourceMaps" value="false"/>
</module>
diff --git a/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml b/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
index c4cbd6c..a829874 100644
--- a/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
+++ b/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
@@ -43,4 +43,26 @@
<when-type-is class="com.google.gwt.core.client.impl.StackTraceCreator.Collector" />
<when-property-is name="compiler.stackMode" value="strip" />
</replace-with>
+
+ <!-- Disable source maps for non-Chrome browsers -->
+ <set-property name="compiler.useSourceMaps" value="false">
+ <none>
+ <when-property-is name="user.agent" value="safari"/>
+ </none>
+ </set-property>
+
+<set-property name="compiler.useSourceMaps" value="false"/>
+
+ <!-- Utility class to query if source maps are enabled, mainly for testing -->
+ <replace-with class="com.google.gwt.core.client.impl.SourceMapProperty.SourceMapEnabled">
+ <when-type-is class="com.google.gwt.core.client.impl.SourceMapProperty.SourceMapImpl"/>
+ <when-property-is name="compiler.useSourceMaps" value="true"/>
+ </replace-with>
+
+ <!-- If stack trace emulation is on, we can still get accurate stack traces even if sourcemaps are off -->
+ <replace-with class="com.google.gwt.core.client.impl.SourceMapProperty.SourceMapEnabled">
+ <when-type-is class="com.google.gwt.core.client.impl.SourceMapProperty.SourceMapImpl"/>
+ <when-property-is name="compiler.useSourceMaps" value="false"/>
+ <when-property-is name="compiler.stackMode" value="emulated"/>
+ </replace-with>
</module>
diff --git a/user/src/com/google/gwt/core/client/impl/SourceMapProperty.java b/user/src/com/google/gwt/core/client/impl/SourceMapProperty.java
new file mode 100644
index 0000000..6e79367
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/SourceMapProperty.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 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.client.impl;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Helper class to query if SourceMaps are enabled and capable of working on the current user
+ * agent.
+ */
+public class SourceMapProperty {
+
+ static class SourceMapEnabled extends SourceMapImpl {
+
+ public native boolean doesBrowserSupportSourceMaps() /*-{
+ // Chrome only for now, future Firefoxes have promised support
+ return navigator.userAgent.indexOf('Chrome') > -1;
+ }-*/;
+
+ public boolean isSourceMapGenerationOn() {
+ return true;
+ }
+ }
+
+ static class SourceMapEmulated extends SourceMapEnabled {
+
+ public boolean shouldUseSourceMaps() {
+ // pretend emulated stack is a sourcemap
+ return true;
+ }
+ }
+
+ /**
+ * Interface to provide both the compile time and runtime <code>user.agent</code> selection
+ * property value.
+ */
+ static class SourceMapImpl {
+
+ public boolean doesBrowserSupportSourceMaps() {
+ return false;
+ }
+
+ public boolean isSourceMapGenerationOn() {
+ return false;
+ }
+
+ public boolean shouldUseSourceMaps() {
+ return isSourceMapGenerationOn() && doesBrowserSupportSourceMaps();
+ }
+ }
+
+ private static final SourceMapImpl IMPL = GWT.create(SourceMapImpl.class);
+
+ public static boolean doesBrowserSupportSourceMaps() {
+ return IMPL.doesBrowserSupportSourceMaps();
+ }
+
+ /**
+ * True if fully accurate stack traces are possible. True for DevMode, emulated stack traces, and
+ * cases where sourceMaps can work with detailed browser stack trace support.
+ */
+ public static boolean isDetailedDeobfuscatedStackTraceSupported() {
+ return !GWT.isScript() || shouldUseSourceMaps();
+ }
+
+ public static boolean isSourceMapGenerationOn() {
+ return IMPL.isSourceMapGenerationOn();
+ }
+
+ public static boolean shouldUseSourceMaps() {
+ return IMPL.shouldUseSourceMaps();
+ }
+}
diff --git a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
index 3aa6182..2a73797 100644
--- a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
+++ b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -26,6 +26,19 @@
* Production Mode.
*/
public class StackTraceCreator {
+
+ /**
+ * Line number used in a stack trace when it is unknown.
+ */
+ public static final int LINE_NUMBER_UNKNOWN = -1;
+
+ /**
+ * Used to encode a (column, line) pair into a line number (since LogRecord only stores line
+ * numbers). We store line, col as line + col * max_line, and use division and modulus to
+ * recover on the server side.
+ */
+ public static final int MAX_LINE_NUMBER = 100000;
+
/**
* This class acts as a deferred-binding hook point to allow more optimal
* versions to be substituted. This base version simply crawls
@@ -107,7 +120,7 @@
/**
* Attempt to infer the stack from an unknown JavaScriptObject that had been
* thrown. The default implementation just returns an empty array.
- *
+ *
* @param e a JavaScriptObject
*/
public JsArrayString inferFrom(JavaScriptObject e) {
@@ -245,12 +258,12 @@
/**
* Chrome uses a slightly different format to Mozilla.
- *
+ *
* See http://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/
* messages.js?r=2340#712 for formatting code.
- *
+ *
* Function calls can be of the four following forms:
- *
+ *
* <pre>
* at file.js:1:2
* at functionName (file.js:1:2)
@@ -259,6 +272,7 @@
* </pre>
*/
static class CollectorChrome extends CollectorMoz {
+
@Override
public JsArrayString collect() {
JsArrayString res = super.collect();
@@ -273,6 +287,18 @@
}
@Override
+ public void createStackTrace(JavaScriptException e) {
+ JsArrayString stack = inferFrom(e.getException());
+ parseStackTrace(e, stack);
+ }
+
+ @Override
+ public void fillInStackTrace(Throwable t) {
+ JsArrayString stack = StackTraceCreator.createStackTrace();
+ parseStackTrace(t, stack);
+ }
+
+ @Override
public JsArrayString inferFrom(JavaScriptObject e) {
JsArrayString stack = super.inferFrom(e);
if (stack.length() == 0) {
@@ -286,8 +312,11 @@
@Override
protected String extractName(String fnToString) {
+ String extractedName = "anonymous";
+ String location = "";
+
if (fnToString.length() == 0) {
- return "anonymous";
+ return extractedName;
}
String toReturn = fnToString.trim();
@@ -297,34 +326,70 @@
toReturn = toReturn.substring(3);
}
- // Strip bracketed items from the end:
+ // Strip square bracketed items from the end:
int index = toReturn.indexOf("[");
- if (index == -1) {
- index = toReturn.indexOf("(");
+ if (index != -1) {
+ toReturn = toReturn.substring(0, index).trim() +
+ toReturn.substring(toReturn.indexOf("]", index) + 1).trim();
}
+
+ index = toReturn.indexOf("(");
if (index == -1) {
- // No bracketed items found, hence no function name available:
- return "anonymous";
+ // No bracketed items found, hence no function name available
+ location = toReturn;
+ toReturn = "";
} else {
- // Bracketed items found: strip them off.
+ // Bracketed items found: strip them off, parse location info
+ int closeParen = toReturn.indexOf(")", index);
+ location = toReturn.substring(index + 1, closeParen);
toReturn = toReturn.substring(0, index).trim();
}
- // Strip the Type off to leave just the functionName:
+ // Strip the Type off t
index = toReturn.indexOf('.');
if (index != -1) {
toReturn = toReturn.substring(index + 1);
}
-
- return toReturn.length() > 0 ? toReturn : "anonymous";
+ return (toReturn.length() > 0 ? toReturn : "anonymous") + "@@" + location;
}
@Override
protected int toSplice() {
return 3;
}
+
+ private void parseStackTrace(Throwable e, JsArrayString stack) {
+ StackTraceElement[] stackTrace = new StackTraceElement[stack.length()];
+ for (int i = 0, j = stackTrace.length; i < j; i++) {
+ String stackElements[] = stack.get(i).split("@@");
+
+ int line = LINE_NUMBER_UNKNOWN;
+ int col = -1;
+ String fileName = "Unknown";
+ if (stackElements.length == 2 && stackElements[1] != null) {
+ String location = stackElements[1];
+ // colon between line and column
+ int lastColon = location.lastIndexOf(':');
+ // colon between file url and line number
+ int endFileUrl = location.lastIndexOf(':', lastColon - 1);
+ fileName = location.substring(0, endFileUrl);
+
+ if (lastColon != -1 && endFileUrl != -1) {
+ line = parseInt(location.substring(endFileUrl + 1, lastColon));
+ col = parseInt(location.substring(lastColon + 1));
+ }
+ }
+ stackTrace[i] = new StackTraceElement("Unknown", stackElements[0], fileName,
+ line < 0 ? -1 : col * MAX_LINE_NUMBER + line);
+ }
+ e.setStackTrace(stackTrace);
+ }
}
+ private static native int parseInt(String number) /*-{
+ return parseInt(number) || -1;
+ }-*/;
+
/**
* Opera encodes stack trace information in the error's message.
*/
@@ -392,8 +457,6 @@
// empty, since Throwable.getStackTrace() properly handles null
}
}
-
- private static final int LINE_NUMBER_UNKNOWN = -1;
/**
* Create a stack trace based on a JavaScriptException. This method should
diff --git a/user/src/com/google/gwt/junit/JUnit.gwt.xml b/user/src/com/google/gwt/junit/JUnit.gwt.xml
index 29cdbc4..0e30089 100644
--- a/user/src/com/google/gwt/junit/JUnit.gwt.xml
+++ b/user/src/com/google/gwt/junit/JUnit.gwt.xml
@@ -34,7 +34,12 @@
<!-- We want to provide consistent stack traces across all browsers. -->
<set-configuration-property name="compiler.emulatedStack.recordFileNames" value="true" />
- <set-property name="compiler.stackMode" value="emulated" />
+ <set-property name="compiler.stackMode" value="emulated">
+ <none>
+ <!-- Don't use stack trace emulation if source maps enabled -->
+ <when-property-is name="compiler.useSourceMaps" value="true"/>
+ </none>
+ </set-property>
<!-- Override the regular symbolMaps linker to put the data somewhere we can find it -->
<define-linker name="symbolMaps" class="com.google.gwt.junit.linker.JUnitSymbolMapsLinker" />
diff --git a/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java b/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java
index 723654c..4638ec0 100644
--- a/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java
+++ b/user/src/com/google/gwt/junit/linker/JUnitSymbolMapsLinker.java
@@ -21,6 +21,7 @@
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.Shardable;
+import com.google.gwt.core.ext.linker.SyntheticArtifact;
import com.google.gwt.core.linker.SymbolMapsLinker;
import java.io.ByteArrayOutputStream;
@@ -31,12 +32,15 @@
*/
@Shardable
public class JUnitSymbolMapsLinker extends SymbolMapsLinker {
+
+ public static final String SYMBOL_MAP_DIR = ".junit_symbolMaps/";
+
@Override
protected void doEmitSymbolMap(TreeLogger logger, ArtifactSet artifacts,
CompilationResult result, ByteArrayOutputStream out)
throws UnableToCompleteException {
// Collaborate with JUnitHostImpl.loadSymbolMap
- String partialPath = ".junit_symbolMaps/" + result.getStrongName()
+ String partialPath = SYMBOL_MAP_DIR + result.getStrongName()
+ STRONG_NAME_SUFFIX;
EmittedArtifact symbolMapArtifact = emitBytes(logger, out.toByteArray(),
@@ -45,4 +49,9 @@
artifacts.add(symbolMapArtifact);
}
+ @Override
+ protected SyntheticArtifact emitSourceMapString(TreeLogger logger, String contents,
+ String partialPath) throws UnableToCompleteException {
+ return emitString(logger, contents, SYMBOL_MAP_DIR + partialPath);
+ }
}
diff --git a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
index 9f79b68..ef26877 100644
--- a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
+++ b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
@@ -233,7 +233,7 @@
if (symbolData != null) {
// jsniIdent, className, memberName, sourceUri, sourceLine
String[] parts = symbolData.split(",");
- assert parts.length == 5 : "Expected 5, have " + parts.length;
+ assert parts.length == 6 : "Expected 6, have " + parts.length;
JsniRef ref = JsniRef.parse(parts[0].substring(0,
parts[0].lastIndexOf(')') + 1));
diff --git a/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java b/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
index b8f7dfd..adb20bc 100644
--- a/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
+++ b/user/src/com/google/gwt/logging/server/JsonLogRecordServerUtil.java
@@ -71,12 +71,16 @@
Throwable cause =
throwableFromJson(t.getString("cause"));
StackTraceElement[] stackTrace = null;
- JSONArray st = t.getJSONArray("stackTrace");
- if (st.length() > 0) {
- stackTrace = new StackTraceElement[st.length()];
- for (int i = 0; i < st.length(); i++) {
- stackTrace[i] = stackTraceElementFromJson(st.getString(i));
+ if (t.has("stackTrace")) {
+ JSONArray st = t.getJSONArray("stackTrace");
+ if (st.length() > 0) {
+ stackTrace = new StackTraceElement[st.length()];
+ for (int i = 0; i < st.length(); i++) {
+ stackTrace[i] = stackTraceElementFromJson(st.getString(i));
+ }
}
+ } else {
+ stackTrace = new StackTraceElement[0];
}
Throwable thrown = new Throwable(message, cause);
thrown.setStackTrace(stackTrace);
diff --git a/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java b/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java
index 472afaf..545b6c4 100644
--- a/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java
+++ b/user/src/com/google/gwt/logging/server/StackTraceDeobfuscator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -16,6 +16,14 @@
package com.google.gwt.logging.server;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapConsumerFactory;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapConsumerV3;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapping;
+import com.google.gwt.thirdparty.debugging.sourcemap.proto.Mapping;
+import com.google.gwt.core.client.impl.StackTraceCreator;
+
+import org.json.JSONObject;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -24,62 +32,70 @@
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
+import java.util.Scanner;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * Deobfuscates stack traces on the server side. This class requires that you
- * have turned on emulated stack traces, via
- * <code><set-property name="compiler.stackMode" value="emulated" /></code>
- * in your <code>.gwt.xml</code> module file, and moved your symbol map files to
- * a location accessible by your server sever side code. You can use the GWT
- * compiler <code>-deploy</code> command line argument to specify the location
- * of the folder into which the generated <code>symbolMaps</code> directory is
- * written. By default, the final <code>symbolMaps</code> directory is
- * <code>war/WEB-INF/deploy/<i>yourmodulename</i>/symbolMaps/</code>. Pass the
- * resulting directory location into this class'
- * {@link StackTraceDeobfuscator#symbolMapsDirectory} constructor or
- * {@link #setSymbolMapsDirectory(String)} setter method.
- *
+ * Deobfuscates stack traces on the server side. This class requires that you have turned on
+ * emulated stack traces, via <code><set-property name="compiler.stackMode" value="emulated"
+ * /></code> in your <code>.gwt.xml</code> module file for non-Chrome browsers or
+ * <code><set-property name="compiler.useSourceMaps" value="true"/></code> for Chrome, and
+ * moved your symbol map files to a location accessible by your server sever side code. You can use
+ * the GWT compiler <code>-deploy</code> command line argument to specify the location of the folder
+ * into which the generated <code>symbolMaps</code> directory is written. By default, the final
+ * <code>symbolMaps</code> directory is <code>war/WEB-INF/deploy/<i>yourmodulename</i>/symbolMaps/</code>.
+ * Pass the resulting directory location into this class' {@link StackTraceDeobfuscator#symbolMapsDirectory}
+ * constructor or {@link #setSymbolMapsDirectory(String)} setter method.
+ *
* TODO(unnurg): Combine this code with similar code in JUnitHostImpl
*/
public class StackTraceDeobfuscator {
-
- private static class SymbolMap extends HashMap<String, String> { }
-
+
+ private static class SymbolMap extends HashMap<String, String> {
+ }
+
// From JsniRef class, which is in gwt-dev and so can't be accessed here
// TODO(unnurg) once there is a place for shared code, move this to there.
private static Pattern JsniRefPattern =
- Pattern.compile("@?([^:]+)::([^(]+)(\\((.*)\\))?");
-
- // The javadoc for StackTraceElement.getLineNumber() says it returns -1 when
+ Pattern.compile("@?([^:]+)::([^(]+)(\\((.*)\\))?");
+
+ // The javadoc for StackTraceElement.getLineNumber() says it returns -1 when
// the line number is unavailable
private static final int LINE_NUMBER_UNKNOWN = -1;
-
- private File symbolMapsDirectory;
-
+
+ Pattern fragmentIdPattern = Pattern.compile(".*(\\d+)\\.js");
+
+ protected File symbolMapsDirectory;
+
+ // Map of strongName + fragmentId to sourceMap
+ private Map<String, SourceMapping> sourceMaps =
+ new HashMap<String, SourceMapping>();
+
+ private Map<String, Integer> sourceMapPrefixes = new HashMap<String, Integer>();
+
private Map<String, SymbolMap> symbolMaps =
- new HashMap<String, SymbolMap>();
+ new HashMap<String, SymbolMap>();
/**
- * Constructor, which takes a <code>symbolMaps</code> directory as its
- * argument. Symbol maps are generated into the location specified by the
- * GWT compiler <code>-deploy</code> command line argument.
- *
- * @param symbolMapsDirectory the <code>symbolMaps</code> directory with, or
- * without trailing directory separator character
+ * Constructor, which takes a <code>symbolMaps</code> directory as its argument. Symbol maps are
+ * generated into the location specified by the GWT compiler <code>-deploy</code> command line
+ * argument.
+ *
+ * @param symbolMapsDirectory the <code>symbolMaps</code> directory with, or without trailing
+ * directory separator character
*/
public StackTraceDeobfuscator(String symbolMapsDirectory) {
setSymbolMapsDirectory(symbolMapsDirectory);
}
-
+
/**
* Best effort resymbolization of a log record's stack trace.
- *
- * @param lr the log record to resymbolize
+ *
+ * @param lr the log record to resymbolize
* @param strongName the GWT permutation strong name
- * @return the best effort resymbolized log record
+ * @return the best effort resymbolized log record JJSO
*/
public LogRecord deobfuscateLogRecord(LogRecord lr, String strongName) {
if (lr.getThrown() != null && strongName != null) {
@@ -87,12 +103,11 @@
}
return lr;
}
-
+
/**
- * Convenience method which resymbolizes an entire stack trace to extent
- * possible.
+ * Convenience method which resymbolizes an entire stack trace to extent possible.
*
- * @param st the stack trace to resymbolize
+ * @param st the stack trace to resymbolize
* @param strongName the GWT permutation strong name
* @return a best effort resymbolized stack trace
*/
@@ -104,28 +119,34 @@
}
return newSt;
}
-
+
/**
* Best effort resymbolization of a a single stack trace element.
*
- * @param ste the stack trace element to resymbolize
+ * @param ste the stack trace element to resymbolize
* @param strongName the GWT permutation strong name
* @return the best effort resymbolized stack trace element
*/
public StackTraceElement resymbolize(StackTraceElement ste,
String strongName) {
+ String declaringClass = null;
+ String methodName = null;
+ String filename = null;
+ int lineNumber = -1;
+ int fragmentId = -1;
+
+ String steFilename = ste.getFileName();
SymbolMap map = loadSymbolMap(strongName);
String symbolData = map == null ? null : map.get(ste.getMethodName());
+ // first use symbolMap, then refine via sourceMap if possible
if (symbolData != null) {
- // jsniIdent, className, memberName, sourceUri, sourceLine
+ // jsniIdent, className, memberName, sourceUri, sourceLine, fragmentId
String[] parts = symbolData.split(",");
- if (parts.length == 5) {
+ if (parts.length == 6) {
String[] ref = parse(
parts[0].substring(0, parts[0].lastIndexOf(')') + 1));
- String declaringClass;
- String methodName;
if (ref != null) {
declaringClass = ref[0];
methodName = ref[1];
@@ -133,12 +154,13 @@
declaringClass = ste.getClassName();
methodName = ste.getMethodName();
}
-
+
// parts[3] contains the source file URI or "Unknown"
- String filename = "Unknown".equals(parts[3]) ? null
+ filename = "Unknown".equals(parts[3]) ? null
: parts[3].substring(parts[3].lastIndexOf('/') + 1);
-
- int lineNumber = ste.getLineNumber();
+
+ lineNumber = ste.getLineNumber();
+
/*
* When lineNumber is LINE_NUMBER_UNKNOWN, either because
* compiler.stackMode is not emulated or
@@ -148,30 +170,78 @@
if (lineNumber == LINE_NUMBER_UNKNOWN) {
lineNumber = Integer.parseInt(parts[4]);
}
-
- return new StackTraceElement(
- declaringClass, methodName, filename, lineNumber);
+
+ fragmentId = Integer.parseInt(parts[5]);
}
}
+
+ // anonymous function, try to use <fragmentNum>.js:line:col to lookup function
+ if (fragmentId == -1 && steFilename != null) {
+ // fragment identifier encoded in filename
+ Matcher matcher = fragmentIdPattern.matcher(steFilename);
+ if (matcher.matches()) {
+ String fragment = matcher.group(1);
+ try {
+ fragmentId = Integer.parseInt(fragment);
+ } catch (Exception e) {
+ }
+ } else if (steFilename.contains(strongName)) {
+ // else it's <strongName>.cache.js which is the 0th fragment
+ fragmentId = 0;
+ }
+ }
+
+ // try to refine location via sourcemap
+ if (fragmentId != -1) {
+ SourceMapping sourceMapping = loadSourceMap(strongName, fragmentId);
+ if (sourceMapping != null && ste.getLineNumber() > -1) {
+ int column = 1;
+ int jsLineNumber = ste.getLineNumber();
+ if (jsLineNumber > StackTraceCreator.MAX_LINE_NUMBER) {
+ column = jsLineNumber / StackTraceCreator.MAX_LINE_NUMBER;
+ jsLineNumber = jsLineNumber % StackTraceCreator.MAX_LINE_NUMBER;
+ }
+ Mapping.OriginalMapping mappingForLine = sourceMapping
+ .getMappingForLine(jsLineNumber, column);
+ if (mappingForLine != null) {
+
+ if (declaringClass == null || declaringClass.equals(ste.getClassName())) {
+ declaringClass = mappingForLine.getOriginalFile();
+ methodName = mappingForLine.getIdentifier();
+ }
+ filename = mappingForLine.getOriginalFile();
+ lineNumber = mappingForLine.getLineNumber() - 1;
+ }
+ }
+ }
+
+ if (declaringClass != null) {
+ return new StackTraceElement(declaringClass, methodName, filename, lineNumber);
+ }
+
// If anything goes wrong, just return the unobfuscated element
return ste;
}
-
+
public void setSymbolMapsDirectory(String symbolMapsDirectory) {
// permutations are unique, no need to clear the symbolMaps hash map
this.symbolMapsDirectory = new File(symbolMapsDirectory);
}
+ protected InputStream getSourceMapInputStream(String permutationStrongName, int fragmentNumber)
+ throws IOException {
+ String filename = symbolMapsDirectory.getCanonicalPath()
+ + File.separatorChar + permutationStrongName + "_sourceMap" + fragmentNumber + ".json";
+ return new FileInputStream(filename);
+ }
+
/**
- * Retrieves a new {@link InputStream} for the given permutation strong name.
- * This implementation, which subclasses may override, returns a
- * {@link InputStream} for the <code>
- * <i>permutation-strong-name</i>.symbolMap</code> file in the
- * <code>symbolMaps</code> directory.
+ * Retrieves a new {@link InputStream} for the given permutation strong name. This implementation,
+ * which subclasses may override, returns a {@link InputStream} for the <code>
+ * <i>permutation-strong-name</i>.symbolMap</code> file in the <code>symbolMaps</code> directory.
*
* @param permutationStrongName the GWT permutation strong name
* @return a new {@link InputStream}
- * @throws IOException
*/
protected InputStream getSymbolMapInputStream(String permutationStrongName)
throws IOException {
@@ -179,7 +249,7 @@
+ File.separatorChar + permutationStrongName + ".symbolMap";
return new FileInputStream(filename);
}
-
+
private Throwable deobfuscateThrowable(Throwable old, String strongName) {
Throwable t = new Throwable(old.getMessage());
if (old.getStackTrace() != null) {
@@ -192,7 +262,27 @@
}
return t;
}
-
+
+ private SourceMapping loadSourceMap(String permutationStrongName, int fragmentId) {
+ SourceMapping toReturn = sourceMaps.get(permutationStrongName + fragmentId);
+ if (toReturn == null) {
+ try {
+ SourceMapConsumerV3 consumer = new SourceMapConsumerV3();
+ String sourceMapString = loadStreamAsString(
+ getSourceMapInputStream(permutationStrongName, fragmentId));
+ JSONObject obj = new JSONObject(sourceMapString);
+ toReturn = SourceMapConsumerFactory.parse(sourceMapString);
+ sourceMaps.put(permutationStrongName + fragmentId, toReturn);
+ } catch (Exception e) {
+ }
+ }
+ return toReturn;
+ }
+
+ private String loadStreamAsString(InputStream stream) {
+ return new Scanner(stream).useDelimiter("\\A").next();
+ }
+
private SymbolMap loadSymbolMap(
String strongName) {
SymbolMap toReturn = symbolMaps.get(strongName);
@@ -212,7 +302,7 @@
}
int idx = line.indexOf(',');
toReturn.put(new String(line.substring(0, idx)),
- line.substring(idx + 1));
+ line.substring(idx + 1));
}
} finally {
bin.close();
@@ -227,13 +317,13 @@
}
/**
- * Extracts the declaring class and method name from a JSNI ref, or null if
- * the information cannot be extracted.
+ * Extracts the declaring class and method name from a JSNI ref, or null if the information cannot
+ * be extracted.
*
- * @see com.google.gwt.dev.util.JsniRef
* @param refString symbol map reference string
- * @return a string array contains the declaring class and method name, or
- * null when the regex match fails
+ * @return a string array contains the declaring class and method name, or null when the regex
+ * match fails
+ * @see com.google.gwt.dev.util.JsniRef
*/
private String[] parse(String refString) {
Matcher matcher = JsniRefPattern.matcher(refString);
@@ -242,7 +332,7 @@
}
String className = matcher.group(1);
String memberName = matcher.group(2);
- String[] toReturn = new String[] {className, memberName};
+ String[] toReturn = new String[]{className, memberName};
return toReturn;
}
}
diff --git a/user/src/com/google/gwt/user/tools/QuerySourceMap.java b/user/src/com/google/gwt/user/tools/QuerySourceMap.java
new file mode 100644
index 0000000..8d7b3a8
--- /dev/null
+++ b/user/src/com/google/gwt/user/tools/QuerySourceMap.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.user.tools;
+
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapConsumerFactory;
+import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapping;
+import com.google.gwt.dev.util.Util;
+
+import java.io.File;
+
+/**
+ * Command-line utility for querying source maps.
+ */
+public class QuerySourceMap {
+
+ public static void main(String[] args) throws Exception {
+ String filename = args[0];
+ int line = Integer.valueOf(args[1]);
+ int col = Integer.valueOf(args[2]);
+
+ SourceMapping consumer = SourceMapConsumerFactory.parse(Util.readFileAsString(new File(filename)));
+ System.out.println(consumer.getMappingForLine(line, col));
+ }
+}
diff --git a/user/test/com/google/gwt/core/client/impl/StackTraceCreatorTest.java b/user/test/com/google/gwt/core/client/impl/StackTraceCreatorTest.java
index e57776a..cd7e41d 100644
--- a/user/test/com/google/gwt/core/client/impl/StackTraceCreatorTest.java
+++ b/user/test/com/google/gwt/core/client/impl/StackTraceCreatorTest.java
@@ -185,12 +185,12 @@
public void testChromeExtractName() {
CollectorChrome c = new CollectorChrome();
- assertEquals("anonymous", c.extractName(" at file.js:1:2"));
- assertEquals("functionName",
+ assertEquals("anonymous@@file.js:1:2", c.extractName(" at file.js:1:2"));
+ assertEquals("functionName@@file.js:1:2",
c.extractName(" at functionName (file.js:1:2)"));
- assertEquals("functionName",
+ assertEquals("functionName@@file.js:1:2",
c.extractName(" at Type.functionName (file.js:1:2)"));
- assertEquals("functionName",
+ assertEquals("functionName@@file.js:1:2",
c.extractName(" at Type.functionName [as methodName] (file.js:1:2)"));
}
}
diff --git a/user/test/com/google/gwt/user/LoggingRPCSuite.gwt.xml b/user/test/com/google/gwt/user/LoggingRPCSuite.gwt.xml
index 6f589f6..d8f2109 100644
--- a/user/test/com/google/gwt/user/LoggingRPCSuite.gwt.xml
+++ b/user/test/com/google/gwt/user/LoggingRPCSuite.gwt.xml
@@ -21,5 +21,32 @@
<servlet path='/loggingrpc'
class='com.google.gwt.user.server.rpc.LoggingRPCTestServiceImpl' />
+ <servlet path="/remote_logging" class="com.google.gwt.logging.server.RemoteLoggingServiceImpl"/>
+
+ <set-property name="gwt.logging.logLevel" value="WARNING"/>
+ <set-property name="gwt.logging.enabled" value="TRUE"/>
+ <set-property name="gwt.logging.consoleHandler" value="ENABLED"/>
+ <set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />
+
+
+ <set-property name="compiler.useSourceMaps" value="true"/>
+
+
+ <!-- Turn off emulation to test non-sourcemap mode -->
+ <set-property name="compiler.stackMode" value="native"/>
+ <!-- when Chrome is enabled, turn off stack trace emulation -->
+ <set-property name="compiler.stackMode" value="strip">
+ <when-property-is name="user.agent" value="safari"/>
+ </set-property>
+
+ <!-- when stack trace stripping is enabled, we need to replace the Null collector
+ with one that does something for Chrome -->
+ <replace-with class="com.google.gwt.core.client.impl.StackTraceCreator.CollectorChrome">
+ <when-type-is class="com.google.gwt.core.client.impl.StackTraceCreator.Collector" />
+ <!-- For now, only Chrome provides Error.stack support, so we hijack the
+ entire WebKit permutation -->
+ <when-property-is name="user.agent" value="safari" />
+ <when-property-is name="compiler.useSourceMaps" value="true" />
+ </replace-with>
</module>
diff --git a/user/test/com/google/gwt/user/client/rpc/LoggingRPCTest.java b/user/test/com/google/gwt/user/client/rpc/LoggingRPCTest.java
index 365ce4a..8991ffa 100644
--- a/user/test/com/google/gwt/user/client/rpc/LoggingRPCTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/LoggingRPCTest.java
@@ -18,21 +18,34 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.core.client.impl.SourceMapProperty;
import java.util.logging.Level;
import java.util.logging.LogRecord;
/**
* Test cases for Generic Collections in GWT RPC.
- *
*/
public class LoggingRPCTest extends GWTTestCase {
+ /**
+ * WARNING! WARNING! If you edit this method or insert any lines of code above this method,you
+ * must re-edit the line number constants below;
+ */
+ private static final int METHOD_START_LINE = 39;
+
+ private static final int METHOD_EXCEPTION_LINE = 41;
+
+ private void throwException(String arg) {
+ // prevent inlining by double referencing arg
+ throw new RuntimeException(arg.charAt(0) + arg.substring(1));
+ }
+
@Override
public String getModuleName() {
return "com.google.gwt.user.LoggingRPCSuite";
}
-
+
private static final LogRecord expectedLogRecord = createLogRecord();
public static boolean isValid(LogRecord value) {
@@ -64,7 +77,7 @@
}
// Do not compare trace as it is not stable across RPC.
-
+
expectedCause = expectedCause.getCause();
valueCause = valueCause.getCause();
}
@@ -90,6 +103,18 @@
return result;
}
+ private static LogRecord createLogRealRecord(Throwable thrown) {
+ LogRecord result = new LogRecord(Level.INFO, "Test Log Record");
+
+ // Only set serialized fields.
+ result.setLoggerName("Test Logger Name");
+ result.setMillis(1234567);
+
+ result.setThrown(thrown);
+
+ return result;
+ }
+
private LoggingRPCTestServiceAsync loggingRPCTestService;
public void testLogRecord() {
@@ -109,10 +134,65 @@
});
}
+ public void testStackMapDeobfuscation() {
+ final LoggingRPCTestServiceAsync service = getServiceAsync();
+ delayTestFinish(500000);
+ GWT.runAsync(LoggingRPCTest.class, new com.google.gwt.core.client.RunAsyncCallback() {
+
+ @Override
+ public void onFailure(Throwable reason) {
+ }
+
+ @Override
+ public void onSuccess() {
+ try {
+ throwException("Runtime Exception");
+ } catch (Exception e) {
+ service.deobfuscateLogRecord(createLogRealRecord(e), new AsyncCallback<LogRecord>() {
+
+ public void onFailure(Throwable caught) {
+ assertTrue(false);
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(LogRecord record) {
+ Throwable thrown = record.getThrown();
+ boolean found = false;
+ for (StackTraceElement e : thrown.getStackTrace()) {
+ if (e.getFileName().contains("LoggingRPCTest")) {
+ // if DevMode or SourceMaps enabled and Chrome is the browser, check for exact line
+ if (SourceMapProperty.isDetailedDeobfuscatedStackTraceSupported()) {
+ assertEquals(METHOD_EXCEPTION_LINE, e.getLineNumber());
+ } else {
+ // else fallback to line number of method itself
+ assertEquals(METHOD_START_LINE, e.getLineNumber());
+ }
+ String methodName = "throwException";
+ assertTrue("Method name mismatch, expected = " + methodName
+ + " vs actual = " + e.getMethodName(),
+ e.getMethodName().contains(methodName));
+ String className = "com.google.gwt.user.client.rpc.LoggingRPCTest";
+ assertTrue("Class name mismatch, expected = " + className
+ + " actual = " + e.getClassName(), className.contains(e.getClassName()));
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ finishTest();
+ }
+ });
+ }
+ }
+ });
+ }
+
private LoggingRPCTestServiceAsync getServiceAsync() {
if (loggingRPCTestService == null) {
loggingRPCTestService = (LoggingRPCTestServiceAsync) GWT.create(LoggingRPCTestService.class);
}
return loggingRPCTestService;
}
+
+
}
diff --git a/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestService.java b/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestService.java
index f597cd2..1cc4842 100644
--- a/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestService.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -34,6 +34,7 @@
super(msg);
}
}
+ LogRecord deobfuscateLogRecord(LogRecord value) throws LoggingRPCTestServiceException;
LogRecord echoLogRecord(LogRecord value) throws LoggingRPCTestServiceException;
}
diff --git a/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestServiceAsync.java
index 6e680a0..0460667 100644
--- a/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/LoggingRPCTestServiceAsync.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -23,6 +23,8 @@
* Async interface for serialization of GWT core.java emulations.
*/
public interface LoggingRPCTestServiceAsync {
+ void deobfuscateLogRecord(LogRecord value, AsyncCallback<LogRecord> callback);
void echoLogRecord(LogRecord value, AsyncCallback<LogRecord> callback);
+
}
diff --git a/user/test/com/google/gwt/user/server/rpc/LoggingRPCTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/LoggingRPCTestServiceImpl.java
index cb61242..691139a 100644
--- a/user/test/com/google/gwt/user/server/rpc/LoggingRPCTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/LoggingRPCTestServiceImpl.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -16,18 +16,80 @@
package com.google.gwt.user.server.rpc;
+import static com.google.gwt.user.client.rpc.RpcRequestBuilder.MODULE_BASE_HEADER;
+
+import com.google.gwt.junit.linker.JUnitSymbolMapsLinker;
+import com.google.gwt.logging.server.StackTraceDeobfuscator;
import com.google.gwt.user.client.rpc.LoggingRPCTest;
import com.google.gwt.user.client.rpc.LoggingRPCTestService;
+import com.google.gwt.user.client.rpc.RpcRequestBuilder;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletRequest;
/**
- * Remote service implementation for serialization of GWT core.java.util.logging
- * emulations.
+ * Remote service implementation for serialization of GWT core.java.util.logging emulations.
*/
public class LoggingRPCTestServiceImpl extends HybridServiceServlet implements
LoggingRPCTestService {
+ @Override
+ public LogRecord deobfuscateLogRecord(LogRecord value)
+ throws LoggingRPCTestServiceException {
+ // don't deobfuscate DevMode, there's no symbol map
+ if ("HostedMode".equals(getPermutationStrongName())) {
+ return value;
+ }
+
+ StackTraceDeobfuscator deobf = new StackTraceDeobfuscator(getSymbolMapsDir()) {
+ @Override
+ protected InputStream getSourceMapInputStream(String permutationStrongName,
+ int fragmentNumber)
+ throws IOException {
+ if (symbolMapsDirectory.exists()) {
+ return super.getSourceMapInputStream(permutationStrongName,
+ fragmentNumber);
+ } else {
+ return getServletContext().getResourceAsStream(
+ getModule() + File.separatorChar + JUnitSymbolMapsLinker.SYMBOL_MAP_DIR
+ + permutationStrongName
+ + "_sourceMap" + fragmentNumber + ".json");
+ }
+ }
+
+ @Override
+ protected InputStream getSymbolMapInputStream(String permutationStrongName)
+ throws IOException {
+ if (symbolMapsDirectory.exists()) {
+ return super.getSymbolMapInputStream(permutationStrongName);
+ } else {
+ String name =
+ getModule() + File.separatorChar + JUnitSymbolMapsLinker.SYMBOL_MAP_DIR
+ + permutationStrongName
+ + ".symbolMap";
+ return getServletContext().getResourceAsStream(name);
+ }
+ }
+ };
+
+ HttpServletRequest threadLocalRequest = getThreadLocalRequest();
+ String strongName = null;
+ if (threadLocalRequest != null) {
+ // can be null during tests
+ strongName = threadLocalRequest.getHeader(RpcRequestBuilder.STRONG_NAME_HEADER);
+ }
+ LogRecord newRecord = deobf.deobfuscateLogRecord(value, strongName);
+ Logger.getLogger(value.getLoggerName()).log(newRecord);
+ return newRecord;
+ }
+
public LogRecord echoLogRecord(LogRecord value) throws LoggingRPCTestServiceException {
/*
* Don't check the stack trace on the server side, because the expected
@@ -41,4 +103,34 @@
return value;
}
+
+ private String getModule() {
+ try {
+ String header = getThreadLocalRequest().getHeader(MODULE_BASE_HEADER);
+ if (header == null) {
+ return null;
+ }
+ String path = new URL(header).getPath();
+ String contextPath = getThreadLocalRequest().getContextPath();
+ if (!path.startsWith(contextPath)) {
+ return null;
+ }
+ path = path.substring(contextPath.length());
+ if (path.endsWith("/")) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return path;
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ private String getSymbolMapsDir() {
+ String path = getModule();
+ if (path == null) {
+ return null;
+ }
+ return "war" + File.separatorChar + getModule() + File.separatorChar
+ + JUnitSymbolMapsLinker.SYMBOL_MAP_DIR;
+ }
}