blob: ee1447a689f6e63553bf734e333c963c1527584d [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.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.js.JsToStringGenerationVisitor;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.TextOutput;
import com.google.gwt.util.tools.Utility;
import java.io.IOException;
/**
* 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 {
@Override
public String getDescription() {
return "Cross-Site-Iframe";
}
@Override
protected String fillSelectionScriptTemplate(StringBuffer ss,
TreeLogger logger, LinkerContext context, ArtifactSet artifacts,
CompilationResult result) throws UnableToCompleteException {
// Must do installScript before installLocation and waitForBodyLoaded
includeJs(ss, logger, getJsInstallScript(context), "__INSTALL_SCRIPT__");
includeJs(ss, logger, getJsInstallLocation(context), "__INSTALL_LOCATION__");
includeJs(ss, logger, getJsWaitForBodyLoaded(context), "__WAIT_FOR_BODY_LOADED__");
// Must do permutations before providers
includeJs(ss, logger, getJsPermutations(context), "__PERMUTATIONS__");
includeJs(ss, logger, getJsProperties(context), "__PROPERTIES__");
includeJs(ss, logger, getJsProcessMetas(context), "__PROCESS_METAS__");
includeJs(ss, logger, getJsComputeScriptBase(context), "__COMPUTE_SCRIPT_BASE__");
includeJs(ss, logger, getJsLoadExternalStylesheets(context), "__LOAD_STYLESHEETS__");
// This Linker does not support <script> tags in the gwt.xml
if (!artifacts.find(ScriptReference.class).isEmpty()) {
logger.log(TreeLogger.ERROR, "The " + getDescription() +
" linker does not support <script> tags in the gwt.xml files");
throw new UnableToCompleteException();
}
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__", getHostedFilename());
return ss.toString();
}
@Override
protected String getCompilationExtension(TreeLogger logger,
LinkerContext context) {
return ".cache.js";
}
@Override
protected String getHostedFilename() {
return "devmode.js";
}
protected String getJsComputeScriptBase(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/computeScriptBase.js";
}
protected String getJsInstallLocation(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/installLocationIframe.js";
}
// If you override this to return installScriptDirect.js, then you should
// also override shouldInstallCode() to return false
protected String getJsInstallScript(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js";
}
protected String getJsLoadExternalStylesheets(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js";
}
protected String getJsPermutations(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/permutations.js";
}
protected String getJsProcessMetas(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/processMetas.js";
}
protected String getJsProperties(LinkerContext context) {
return "com/google/gwt/core/ext/linker/impl/properties.js";
}
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) {
TextOutput out = new DefaultTextOutput(context.isOutputCompact());
// We assume that the $wnd has been set in the same scope as this code is
// executing in. $wnd is the main window which the GWT code is affecting. It
// is also usually the location the bootstrap function was defined in.
// In iframe based linkers, $wnd = window.parent;
// Usually, in others, $wnd = window;
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;");
// 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 getModuleSuffix(TreeLogger logger, LinkerContext context) {
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');");
return out.toString();
}
@Override
protected String getSelectionScriptTemplate(TreeLogger logger,
LinkerContext context) {
return "com/google/gwt/core/linker/CrossSiteIframeTemplate.js";
}
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) {
outputFilename = result.getStrongName() + "." + outputFilename;
}
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 (!shouldOutputPropertyMap(context) ||
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();
mappings = mappings.concat("Devmode:" + getHostedFilename());
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 true;
}
protected boolean shouldOutputPropertyMap(LinkerContext context) {
return false;
}
@Override
protected String wrapPrimaryFragment(TreeLogger logger,
LinkerContext context, String script, ArtifactSet artifacts,
CompilationResult result) {
StringBuffer out = new StringBuffer();
if (shouldIncludeBootstrapInPrimaryFragment(context)) {
try {
out.append(generateSelectionScript(logger, context, artifacts, result));
} catch (UnableToCompleteException e) {
logger.log(TreeLogger.ERROR, "Problem setting up selection script", e);
e.printStackTrace();
}
}
if (shouldInstallCode(context)) {
// Rewrite the code so it can be installed with
// __MODULE_FUNC__.onScriptDownloaded
out.append(context.getModuleFunctionName());
out.append(".onScriptDownloaded(");
out.append(JsToStringGenerationVisitor.javaScriptString(script.toString()));
out.append(")");
} else {
out.append(script.toString());
}
return out.toString();
}
}