blob: 39f4eea3529aa2d4111788b42927cb6bedbcedad [file] [log] [blame]
/*
* 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
* License for the specific language governing permissions and limitations under
* the License.
*/
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.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.ConfigurationProperty;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.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.ScriptReference;
import com.google.gwt.core.ext.linker.Shardable;
import com.google.gwt.core.ext.linker.impl.PropertiesMappingArtifact;
import com.google.gwt.core.ext.linker.impl.PropertiesUtil;
import com.google.gwt.core.ext.linker.impl.ResourceInjectionUtil;
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.TextOutput;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import com.google.gwt.thirdparty.guava.common.base.Splitter;
import com.google.gwt.util.tools.Utility;
import com.google.gwt.util.tools.shared.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
/**
* This linker uses an iframe to hold the code and a script tag to download the
* code. It can download code cross-site, because it uses a script tag to
* download it and because it never uses XHR. The iframe, meanwhile, makes it
* trivial to install additional code as the app runs.
*/
@LinkerOrder(Order.PRIMARY)
@Shardable
public class CrossSiteIframeLinker extends SelectionScriptLinker {
/**
* A configuration property that can be used to have the linker ignore the
* script tags in gwt.xml rather than fail to compile if they are present.
*/
private static final String FAIL_IF_SCRIPT_TAG_PROPERTY = "xsiframe.failIfScriptTag";
@Override
public String getDescription() {
return "Cross-Site-Iframe";
}
@Override
protected String fillSelectionScriptTemplate(StringBuffer ss, TreeLogger logger,
LinkerContext context, ArtifactSet artifacts, CompilationResult result)
throws UnableToCompleteException {
if (shouldUseSelfForWindowAndDocument(context)) {
replaceAll(ss, "__WINDOW_DEF__", "self");
replaceAll(ss, "__DOCUMENT_DEF__", "self");
} else {
replaceAll(ss, "__WINDOW_DEF__", "window");
replaceAll(ss, "__DOCUMENT_DEF__", "document");
}
// Must do installScript before waitForBodyLoaded and we must do
// waitForBodyLoaded before isBodyLoaded
includeJs(ss, logger, getJsInstallScript(context), "__INSTALL_SCRIPT__");
includeJs(ss, logger, getJsWaitForBodyLoaded(context), "__WAIT_FOR_BODY_LOADED__");
includeJs(ss, logger, getJsIsBodyLoaded(context), "__IS_BODY_LOADED__");
// 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__");
includeJs(ss, logger, getJsComputeScriptBase(context), "__COMPUTE_SCRIPT_BASE__");
includeJs(ss, logger, getJsComputeUrlForResource(context), "__COMPUTE_URL_FOR_RESOURCE__");
includeJs(ss, logger, getJsLoadExternalStylesheets(context), "__LOAD_STYLESHEETS__");
includeJs(ss, logger, getJsRunAsync(context), "__RUN_ASYNC__");
includeJs(ss, logger, getJsDevModeRedirectHook(context), "__DEV_MODE_REDIRECT_HOOK__");
// Must go after __DEV_MODE_REDIRECT_HOOK__ because they're found in DevModeRedirectHook.js.
includeJs(ss, logger, getJsDevModeRedirectHookPermitted(context),
"__DEV_MODE_REDIRECT_HOOK_PERMITTED__");
includeJs(ss, logger, getJsDevModeUrlValidation(context), "__DEV_MODE_URL_VALIDATION__");
// This Linker does not support <script> tags in the gwt.xml
SortedSet<ScriptReference> scripts = artifacts.find(ScriptReference.class);
if (!scripts.isEmpty()) {
String list = "";
for (ScriptReference script : scripts) {
list += (script.getSrc() + "\n");
}
boolean failIfScriptTags = true;
for (ConfigurationProperty prop : context.getConfigurationProperties()) {
if (prop.getName().equalsIgnoreCase(FAIL_IF_SCRIPT_TAG_PROPERTY)) {
if (prop.getValues().get(0).equalsIgnoreCase("false")) {
failIfScriptTags = false;
}
}
}
if (failIfScriptTags) {
// CHECKSTYLE_OFF
String msg =
"The " + getDescription()
+ " linker does not support <script> tags in the gwt.xml files, but the"
+ " gwt.xml file (or the gwt.xml files which it includes) contains the"
+ " following script tags: \n" + list
+ "In order for your application to run correctly, you will need to"
+ " include these tags in your host page directly. In order to avoid"
+ " this error, you will need to remove the script tags from the"
+ " gwt.xml file, or add this property to the gwt.xml file:"
+ " <set-configuration-property name='xsiframe.failIfScriptTag' value='FALSE'/>";
// CHECKSTYLE_ON
logger.log(TreeLogger.ERROR, msg);
throw new UnableToCompleteException();
} else {
if (logger.isLoggable(TreeLogger.INFO)) {
logger.log(TreeLogger.INFO, "Ignoring the following script tags in the gwt.xml "
+ "file\n" + list);
}
}
}
ss = ResourceInjectionUtil.injectStylesheets(ss, artifacts);
ss = permutationsUtil.addPermutationsJs(ss, logger, context);
if (result != null) {
replaceAll(ss, "__KNOWN_PROPERTIES__", PropertiesUtil.addKnownPropertiesJs(logger, result));
}
replaceAll(ss, "__MODULE_FUNC__", context.getModuleFunctionName());
replaceAll(ss, "__MODULE_NAME__", context.getModuleName());
replaceAll(ss, "__HOSTED_FILENAME__", getHostedFilenameFull(context));
if (context.isOutputCompact()) {
replaceAll(ss, "__START_OBFUSCATED_ONLY__", "");
replaceAll(ss, "__END_OBFUSCATED_ONLY__", "");
} else {
replaceAll(ss, "__START_OBFUSCATED_ONLY__", "/*");
replaceAll(ss, "__END_OBFUSCATED_ONLY__", "*/");
}
String jsModuleFunctionErrorCatch = getJsModuleFunctionErrorCatch(context);
if (jsModuleFunctionErrorCatch != null) {
replaceAll(ss, "__BEGIN_TRY_BLOCK__", "try {");
replaceAll(ss, "__END_TRY_BLOCK_AND_START_CATCH__", "} catch (moduleError) {");
includeJs(ss, logger, jsModuleFunctionErrorCatch, "__MODULE_FUNC_ERROR_CATCH__");
replaceAll(ss, "__END_CATCH_BLOCK__", "}");
} else {
replaceAll(ss, "__BEGIN_TRY_BLOCK__", "");
replaceAll(ss, "__END_TRY_BLOCK_AND_START_CATCH__", "");
replaceAll(ss, "__MODULE_FUNC_ERROR_CATCH__", "");
replaceAll(ss, "__END_CATCH_BLOCK__", "");
}
return ss.toString();
}
protected boolean getBooleanConfigurationProperty(LinkerContext context,
String name, boolean def) {
String value = getStringConfigurationProperty(context, name, null);
if (value != null) {
if (value.equalsIgnoreCase("true")) {
return true;
} else if (value.equalsIgnoreCase("false")) {
return false;
}
}
return def;
}
@Override
protected String getCompilationExtension(TreeLogger logger, LinkerContext context) {
return ".cache.js";
}
@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
protected String getHostedFilename() {
return "devmode.js";
}
protected String getHostedFilenameFull(LinkerContext context) {
return context.getModuleName() + "." + getHostedFilename();
}
/**
* Returns the name of the {@code ComputeScriptBase} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/computeScriptBase.js"}.
*
* @param context a LinkerContext
*/
protected String getJsComputeScriptBase(LinkerContext context) {
return getStringConfigurationProperty(context, "computeScriptBaseJs",
"com/google/gwt/core/ext/linker/impl/computeScriptBase.js");
}
/**
* Returns the name of the {@code UrlForResource} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/computeUrlForResource.js"}.
*
* @param context a LinkerContext
*/
protected String getJsComputeUrlForResource(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/computeUrlForResource.js";
}
/**
* Returns a JavaScript fragment that starts Super Dev Mode, if enabled.
* (May return either the JavaScript itself or the name of a Java resource ending with ".js".)
*/
protected String getJsDevModeRedirectHook(LinkerContext context) {
// Enable Super Dev Mode for this app if the devModeRedirectEnabled config property is true.
if (getBooleanConfigurationProperty(context, "devModeRedirectEnabled", true)) {
return "com/google/gwt/core/linker/DevModeRedirectHook.js";
} else {
return "";
}
}
/**
* Returns a JavaScript fragment that validates "devModeUrl"&mdash;the URL that Super Dev Mode's
* JavaScript is loaded from&mdash;before it's used. The variable may be modified to change what
* what URL is loaded, or it maybe be set to "" to disable completely.
* (May return either the JavaScript itself or the name of a Java resource ending with ".js".)
*/
protected String getJsDevModeUrlValidation(LinkerContext context) {
// As a default, if the user provides devModeUrlWhitelistRegexp, then we verify that it
// matches devModeUrl.
String regexp = getStringConfigurationProperty(context, "devModeUrlWhitelistRegexp",
"http://(localhost|127\\.0\\.0\\.1)(:\\d+)?/.*");
if (!regexp.isEmpty()) {
return ""
+ "if (!/^" + regexp.replace("/", "\\/") + "$/.test(devModeUrl)) {\n"
+ " if (window.console && console.log) {\n"
+ " console.log('Ignoring non-whitelisted Dev Mode URL: ' + devModeUrl);\n"
+ " }\n"
+ " devModeUrl = '';"
+ "}";
}
return "";
}
/**
* Returns a JavaScript expression that determines whether Super Dev Mode may be turned on
* for the current page. (May return either the JavaScript itself or the name of a Java resource
* ending with ".js".)
*
* <p>The default implementation allows Super Dev Mode only on http: and file: pages. It could be
* overridden to implement a blacklist or whitelist of hostnames where Super Dev Mode may run.
* As a safety precaution, it's recommended to return false for https and for the hostnames in
* URL's visited by end users.
*
* <p>If you override this method to allow https, it probably won't work anyway because
* browsers often disallow loading JavaScript from http URL's into https pages. To make it work,
* you will also have to find a way to run the code server using https.
*/
protected String getJsDevModeRedirectHookPermitted(LinkerContext context) {
return "$wnd.location.protocol == \"http:\" || $wnd.location.protocol == \"file:\"";
}
/**
* Returns the name of the {@code JsInstallLocation} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/installLocationIframe.js"}.
*
* @param context a LinkerContext
*/
protected String getJsInstallLocation(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/installLocationIframe.js";
}
/**
* Returns the name of the {@code JsInstallScript} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js"}.
*
* <p> If you override this to return {@code installScriptDirect.js}, then you
* should also override {@link #shouldInstallCode(LinkerContext)} to return
* {@code false}.
*
* @param context a LinkerContext
*/
protected String getJsInstallScript(LinkerContext context) {
return getStringConfigurationProperty(context, "installScriptJs",
"com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js");
}
/**
* Returns the name of the {@code JsIsBodyLoaded} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/isBodyLoaded.js"}.
*
* @param context a LinkerContext
*/
protected String getJsIsBodyLoaded(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/isBodyLoaded.js";
}
/**
* Returns the name of the {@code JsLoadExternalStylesheets} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js"}.
*
* @param context a LinkerContext
*/
protected String getJsLoadExternalStylesheets(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js";
}
/**
* Returns the name of the {@code JsModuleFunctionErrorCatch} script. By default returns null.
* This script executes if there's an error loading the module function or executing it.
* The error will be available under a local variable named "moduleError". If non-null, the
* module function and the call to the module function will be placed in a try/catch block.
*
* @param context a LinkerContext
*/
protected String getJsModuleFunctionErrorCatch(LinkerContext context) {
return null;
}
/**
* Returns the name of the {@code JsPermutations} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/permutations.js"}.
*
* @param context a LinkerContext
*/
protected String getJsPermutations(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/permutations.js";
}
/**
* Returns the name of the {@code JsProcessMetas} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/processMetas.js"}.
*
* @param context a LinkerContext
*/
protected String getJsProcessMetas(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/processMetas.js";
}
/**
* Returns the name of the {@code JsProperties} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/properties.js"}.
*
* @param context a LinkerContext
*/
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"}.
*
* @param context a LinkerContext
*/
protected String getJsRunAsync(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/runAsync.js";
}
/**
* Returns the name of the {@code JsWaitForBodyLoaded} script. By default,
* returns {@code "com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js"}.
*
* @param context a LinkerContext
*/
protected String getJsWaitForBodyLoaded(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js";
}
@Override
protected String getModulePrefix(TreeLogger logger, LinkerContext context, String strongName)
throws UnableToCompleteException {
TextOutput out = new DefaultTextOutput(context.isOutputCompact());
// $wnd is the main window that the GWT code will affect and also the
// location where the bootstrap function was defined. In iframe-based linkers,
// $wnd is set to window.parent. Usually, in others, $wnd = window.
// By default, $wnd is not set when the module starts, but a replacement for
// installLocationIframe.js may set it.
out.print("var $wnd = $wnd || window.parent;");
out.newlineOpt();
out.print("var __gwtModuleFunction = $wnd." + context.getModuleFunctionName() + ";");
out.newlineOpt();
out.print("var $sendStats = __gwtModuleFunction.__sendStats;");
out.newlineOpt();
out.print("$sendStats('moduleStartup', 'moduleEvalStart');");
out.newlineOpt();
out.print("var $gwt_version = \"" + About.getGwtVersionNum() + "\";");
out.newlineOpt();
out.print("var $strongName = '" + strongName + "';");
out.newlineOpt();
out.print("var $doc = $wnd.document;");
// The functions for runAsync are set up in the bootstrap script so they
// can be overriden in the same way as other bootstrap code is, however
// they will be called from, and expected to run in the scope of the GWT code
// (usually an iframe) so, here we set up those pointers.
out.print("function __gwtStartLoadingFragment(frag) {");
out.newlineOpt();
String fragDir = getFragmentSubdir(logger, context) + '/';
out.print("var fragFile = '" + fragDir + "' + $strongName + '/' + frag + '" + FRAGMENT_EXTENSION + "';");
out.newlineOpt();
out.print("return __gwtModuleFunction.__startLoadingFragment(fragFile);");
out.newlineOpt();
out.print("}");
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
// variables to be available.
out.print("var $stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null;");
out.newlineOpt();
out.print("var $sessionId = $wnd.__gwtStatsSessionId ? $wnd.__gwtStatsSessionId : null;");
out.newlineOpt();
return out.toString();
}
@Override
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');");
out.newlineOpt();
out.print("gwtOnLoad(" + "__gwtModuleFunction.__errFn, " + "__gwtModuleFunction.__moduleName, "
+ "__gwtModuleFunction.__moduleBase, " + "__gwtModuleFunction.__softPermutationId,"
+ "__gwtModuleFunction.__computePropValue);");
out.newlineOpt();
out.print("$sendStats('moduleStartup', 'end');");
writeMagicComments(out, context, 0, strongName);
return out.toString();
}
@Override
protected String getScriptChunkSeparator(TreeLogger logger, LinkerContext context) {
return shouldInstallCode(context) ? "__SCRIPT_CHUNK_SEPARATOR_MARKER__" : "";
}
@Override
protected String getSelectionScriptTemplate(TreeLogger logger, LinkerContext context) {
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()) {
if (property.getName().equals(name) && property.getValues().size() > 0) {
if (property.getValues().get(0) != null) {
return property.getValues().get(0);
}
}
}
return def;
}
protected void includeJs(StringBuffer selectionScript, TreeLogger logger, String jsSource,
String templateVar) throws UnableToCompleteException {
String js;
if (jsSource.endsWith(".js")) {
try {
js = Utility.getFileFromClassPath(jsSource);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to read file: " + jsSource, e);
throw new UnableToCompleteException();
}
} else {
js = jsSource;
}
replaceAll(selectionScript, templateVar, js);
}
@Override
protected void maybeAddHostedModeFile(TreeLogger logger, LinkerContext context,
ArtifactSet artifacts, CompilationResult result) throws UnableToCompleteException {
String filename = getHostedFilename();
if ("".equals(filename)) {
return;
}
// when we're including bootstrap in the primary fragment, we should be
// generating devmode files for each permutation. Otherwise, we generate it
// only in the final link stage.
boolean isSinglePermutation = (result != null);
if (isSinglePermutation != shouldIncludeBootstrapInPrimaryFragment(context)) {
return;
}
long lastModified = System.currentTimeMillis();
StringBuffer buffer =
readFileToStringBuffer("com/google/gwt/core/ext/linker/impl/" + filename, logger);
String outputFilename = filename;
if (result != null) {
// If we're including bootstrap in the primary fragment, we generate a
// devmode.js for each permutation, and it's name is XXX.devmode.js,
// where XXX is the md5 hash for this permutation
outputFilename = result.getStrongName() + "." + outputFilename;
} else {
// If we're not including bootstrap in the primary fragment, we generate
// a devmode.js for this module and it's name is XXX.devmode.js, where
// XXX is the module name.
outputFilename = getHostedFilenameFull(context);
}
replaceAll(buffer, "__MODULE_NAME__", context.getModuleName());
String script =
generatePrimaryFragmentString(logger, context, result, buffer.toString(), 1, artifacts);
EmittedArtifact devArtifact = emitString(logger, script, outputFilename, lastModified);
artifacts.add(devArtifact);
}
// Output compilation-mappings.txt
@Override
protected void maybeOutputPropertyMap(TreeLogger logger, LinkerContext context,
ArtifactSet toReturn) {
if (permutationsUtil.getPermutationsMap() == null
|| permutationsUtil.getPermutationsMap().isEmpty()) {
return;
}
PropertiesMappingArtifact mappingArtifact =
new PropertiesMappingArtifact(CrossSiteIframeLinker.class, permutationsUtil
.getPermutationsMap());
toReturn.add(mappingArtifact);
EmittedArtifact serializedMap;
try {
String mappings = mappingArtifact.getSerialized();
// CHECKSTYLE_OFF
mappings = mappings.concat("Devmode:" + getHostedFilename());
// CHECKSTYLE_ON
serializedMap = emitString(logger, mappings, "compilation-mappings.txt");
// TODO(unnurg): make this Deploy
serializedMap.setVisibility(Visibility.Public);
toReturn.add(serializedMap);
} catch (UnableToCompleteException e) {
e.printStackTrace();
}
}
// If you set this to return true, you should also override
// getJsPermutations() to return permutationsNull.js and
// getJsInstallScript() to return installScriptAlreadyIncluded.js
protected boolean shouldIncludeBootstrapInPrimaryFragment(LinkerContext context) {
return false;
}
protected boolean shouldInstallCode(LinkerContext context) {
return getBooleanConfigurationProperty(context, "installCode", true);
}
/**
* Returns whether to use "self" for $wnd and $doc references. Defaults to false.
* Useful for worker threads.
*/
protected boolean shouldUseSelfForWindowAndDocument(LinkerContext context) {
return false;
}
@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,
StringUtils.javaScriptString(js));
}
@Override
protected String wrapPrimaryFragment(TreeLogger logger, LinkerContext context, String script,
ArtifactSet artifacts, CompilationResult result) throws UnableToCompleteException {
StringBuilder out = new StringBuilder();
if (shouldIncludeBootstrapInPrimaryFragment(context)) {
out.append(generateSelectionScript(logger, context, artifacts, result));
// only needed in SSSS, breaks WebWorker linker if done all the time
out.append("if (" + context.getModuleFunctionName() + ".succeeded) {\n");
}
if (shouldInstallCode(context)) {
// Rewrite the code so it can be installed with
// __MODULE_FUNC__.onScriptDownloaded
out.append(context.getModuleFunctionName());
out.append(".onScriptDownloaded([");
Iterable<String> chunks = Splitter.on(getScriptChunkSeparator(logger, context)).split(script);
List<String> newChunks = new ArrayList<String>();
for (String chunk : chunks) {
newChunks.add(StringUtils.javaScriptString(chunk));
}
out.append(Joiner.on(",\n").join(newChunks));
out.append("]);\n");
} else {
out.append(script);
out.append("\n");
}
if (shouldIncludeBootstrapInPrimaryFragment(context)) {
out.append("}\n");
}
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");
}
}