Better magic comments at the end of JavaScript fragments in CrossSiteIframeLinker
- Switch to '#' instead of '@' since this changed in the sourcemap spec
- Set sourcemap URL for every fragment, not just the primary one
- Add template variable support to the includeSourceMapUrl config property
Template variables:
__MODULE__ substitutes the module name
__HASH__ substitutes the strong name of the fragment
__FRAGMENT__ substitutes the fragment number
This makes it easier to build a URL that a servlet can resolve to the
right sourcemap file.
Public API changes:
- New versions of two methods in SelectionScriptLinker
- Added CrossSiteIframeLinker.getSourceMapUrl()
Change-Id: I2207d2b258df8c1088129017e5039fbfd0edb093
Review-Link: https://gwt-review.googlesource.com/#/c/4891/
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 233c5a7..46023f3 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
@@ -17,6 +17,7 @@
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.Artifact;
@@ -356,7 +357,16 @@
String prefix = getDeferredFragmentPrefix(logger, context, fragment);
b.append(prefix);
b.append(js);
- b.append(getDeferredFragmentSuffix(logger, context, fragment));
+ String suffix = getDeferredFragmentSuffix(logger, context, fragment);
+ if (suffix == null) {
+ suffix = getDeferredFragmentSuffix2(logger, context, fragment, strongName);
+ if (suffix == null) {
+ logger.log(Type.ERROR, "Neither getDeferredFragmentSuffix nor getDeferredFragmentSuffix2 "
+ + "were overridden in linker: " + getClass().getName());
+ throw new UnableToCompleteException();
+ }
+ }
+ b.append(suffix);
SymbolMapsLinker.ScriptFragmentEditsArtifact editsArtifact
= new SymbolMapsLinker.ScriptFragmentEditsArtifact(strongName, fragment);
editsArtifact.prefixLines(prefix);
@@ -394,7 +404,16 @@
artifacts.add(editsArtifact);
b.append(modulePrefix);
b.append(js);
- b.append(getModuleSuffix(logger, context));
+ String suffix = getModuleSuffix(logger, context);
+ if (suffix == null) {
+ suffix = getModuleSuffix2(logger, context, strongName);
+ if (suffix == null) {
+ logger.log(Type.ERROR, "Neither getModuleSuffix nor getModuleSuffix2 were overridden in "
+ + "linker: " + getClass().getName());
+ throw new UnableToCompleteException();
+ }
+ }
+ b.append(suffix);
return wrapPrimaryFragment(logger, context, b.toString(), artifacts, result);
}
@@ -424,8 +443,26 @@
return "";
}
+ /**
+ * Returns the suffix at the end of a JavaScript fragment other than the initial fragment
+ * (deprecated version). The default version returns null, which will cause
+ * {@link #getDeferredFragmentSuffix2} to be called instead. Subclasses should switch to
+ * extending getDeferredFragmentSuffix2.
+ */
+ @Deprecated
protected String getDeferredFragmentSuffix(TreeLogger logger, LinkerContext context,
int fragment) {
+ return null;
+ }
+
+ /**
+ * Returns the suffix at the end of a JavaScript fragment other than the initial fragment
+ * (new version). This method won't be called if {@link #getDeferredFragmentSuffix} is overridden
+ * to return non-null. Subclasses should stop implementing getDeferredFramgnentSuffix and
+ * implement getDeferredFragmentSuffix2 instead.
+ */
+ protected String getDeferredFragmentSuffix2(TreeLogger logger, LinkerContext context,
+ int fragment, String strongName) {
return "";
}
@@ -476,8 +513,26 @@
return getModulePrefix(logger, context, strongName);
}
- protected abstract String getModuleSuffix(TreeLogger logger,
- LinkerContext context) throws UnableToCompleteException;
+ /**
+ * Returns the suffix for the initial JavaScript fragment (deprecated version).
+ * The default returns null, which will cause {@link #getModuleSuffix2} to be called instead.
+ * Subclasses should switch to extending getModuleSuffix2.
+ */
+ @Deprecated
+ protected String getModuleSuffix(TreeLogger logger,
+ LinkerContext context) throws UnableToCompleteException {
+ return null;
+ }
+
+ /**
+ * Returns the suffix for the initial JavaScript fragment (new version). This version
+ * will not be called if {@link #getModuleSuffix} is overridden so that it doesn't return null.
+ * Subclasses should stop implementing getModuleSuffix and implmenet getModuleSuffix2 instead.
+ */
+ protected String getModuleSuffix2(TreeLogger logger,
+ LinkerContext context, String strongName) throws UnableToCompleteException {
+ return null;
+ }
/**
* Some subclasses support "chunking" of the primary fragment. If chunking will be supported, this
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 5507ecd..446a93f 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -192,9 +192,13 @@
return ".cache.js";
}
- protected String getDeferredFragmentSuffix(TreeLogger logger, LinkerContext context,
- int fragment) {
- return "\n//@ sourceURL=" + context.getModuleName() + "-" + fragment + ".js\n";
+ @Override
+ protected String getDeferredFragmentSuffix2(TreeLogger logger, LinkerContext context,
+ int fragment, String strongName) {
+
+ DefaultTextOutput out = new DefaultTextOutput(context.isOutputCompact());
+ writeMagicComments(out, context, fragment, strongName);
+ return out.toString();
}
@Override
@@ -442,7 +446,11 @@
}
@Override
- protected String getModuleSuffix(TreeLogger logger, LinkerContext context) {
+ protected String getModuleSuffix2(TreeLogger logger, LinkerContext context,
+ String strongName) {
+
+ // Note: this method won't be called if getModuleSuffix() is overridden and returns non-null.
+
DefaultTextOutput out = new DefaultTextOutput(context.isOutputCompact());
out.print("$sendStats('moduleStartup', 'moduleEvalEnd');");
@@ -452,20 +460,8 @@
+ "__gwtModuleFunction.__computePropValue);");
out.newlineOpt();
out.print("$sendStats('moduleStartup', 'end');");
- String includeSourceMapUrl = getStringConfigurationProperty(context, "includeSourceMapUrl", "false");
- if (!"false".equalsIgnoreCase(includeSourceMapUrl)) {
- String sourceMapUrl = SymbolMapsLinker.SourceMapArtifact.sourceMapFilenameForFragment(0);
- if (!"true".equalsIgnoreCase(includeSourceMapUrl)) {
- sourceMapUrl = includeSourceMapUrl;
- }
- // The sourceURL magic comment can cause browsers to ignore the X-SourceMap header
- // This magic comment ensures that they can still locate them in that case
- out.print("\n//@ sourceMappingURL=" + sourceMapUrl + " ");
- }
- // Magic comment serves several purposes:
- // 1. renames strongName to a stable name in browser debugger
- // 2. provides name to scripts installed via eval()
- out.print("\n//@ sourceURL=" + context.getModuleName() + "-0.js\n");
+
+ writeMagicComments(out, context, 0, strongName);
return out.toString();
}
@@ -479,6 +475,27 @@
return "com/google/gwt/core/linker/CrossSiteIframeTemplate.js";
}
+ /**
+ * Returns the sourcemap URL that will be put in the comment at the end of a JavaScript
+ * fragment, or null if the comment should be omitted. The default implementation uses
+ * the includeSourceMapUrl config property.
+ */
+ protected String getSourceMapUrl(LinkerContext context, String strongName, int fragmentId) {
+ String val = getStringConfigurationProperty(context, "includeSourceMapUrl", "false");
+
+ if ("false".equalsIgnoreCase(val)) {
+ return null;
+ }
+
+ if ("true".equalsIgnoreCase(val)) {
+ return SymbolMapsLinker.SourceMapArtifact.sourceMapFilenameForFragment(fragmentId);
+ }
+
+ return val.replaceAll("__HASH__", strongName)
+ .replaceAll("__FRAGMENT__", String.valueOf(fragmentId))
+ .replaceAll("__MODULE__", context.getModuleName());
+ }
+
protected String getStringConfigurationProperty(LinkerContext context,
String name, String def) {
for (ConfigurationProperty property : context.getConfigurationProperties()) {
@@ -646,4 +663,24 @@
}
return out.toString();
}
+
+ private void writeMagicComments(DefaultTextOutput out, LinkerContext context, int fragmentId,
+ String strongName) {
+ String sourceMapUrl = getSourceMapUrl(context, strongName, fragmentId);
+ if (sourceMapUrl != null) {
+ // This magic comment determines where a browser debugger looks for a sourcemap,
+ // except that it may be overridden by a "SourceMap" header in the HTTP response when
+ // loading the JavaScript.
+ // (Note: even if you're using the HTTP header, you still have to set this to an arbitrary
+ // value, or Chrome won't enable sourcemaps.)
+ out.print("\n//# sourceMappingURL=" + sourceMapUrl + " ");
+ }
+
+ // This magic comment determines the name of the JavaScript fragment in a browser debugger.
+ // (In Chrome it typically shows up under "(no domain)".)
+ // We need to set it explicitly because the JavaScript code may be installed via an "eval"
+ // statement and even if we're not using an eval, the filename contains the strongname which
+ // isn't stable across recompiles.
+ out.print("\n//# sourceURL=" + context.getModuleName() + "-" + fragmentId + ".js\n");
+ }
}
diff --git a/user/src/com/google/gwt/core/CrossSiteIframeLinker.gwt.xml b/user/src/com/google/gwt/core/CrossSiteIframeLinker.gwt.xml
index b359425..08a4f14 100644
--- a/user/src/com/google/gwt/core/CrossSiteIframeLinker.gwt.xml
+++ b/user/src/com/google/gwt/core/CrossSiteIframeLinker.gwt.xml
@@ -30,9 +30,16 @@
- debugging in browsers.
- Specification: http://goo.gl/ZQ3V3
- Takes on the following values:
- - if false, no sourcemap URL declarations are included at the end of the primary fragment script
- - if true, a relative URL to the standard sourcemap for the primary fragment is included
- - else the value is assumed to be a custom URL -->
+ - if "false":
+ - no sourcemap URL declarations are included at the end of the primary fragment script
+ - if "true":
+ - a relative URL to the standard sourcemap for the primary fragment is included.
+ - Otherwise:
+ - The value is assumed to be a custom URL. The following tokens will be substituted:
+ - __MODULE__ is replaced with the module name
+ - __FRAGMENT__ is replaced with the fragment id, which will be 0 for the initial fragment.
+ - __HASH__ is replaced with the strong name of the fragment.
+ -->
<define-configuration-property name="includeSourceMapUrl" is-multi-valued="false"/>
<set-configuration-property name="includeSourceMapUrl" value="false"/>
<set-configuration-property name="xsiframe.failIfScriptTag" value="TRUE"/>