Provides a wrapper around transforming a resource name (filename or URL)
into an absolute URL.  By default, this tacks on the module base
to a relative URL and leaves other names alone.  It could be used
in other environments to wrap the request with a proxy, such as
when a GWT module runs as a Gadget.

Review at http://gwt-code-reviews.appspot.com/1385801

Review by: unnurg@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9848 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/ResourceInjectionUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/ResourceInjectionUtil.java
index 4691444..1c5fa8f 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/ResourceInjectionUtil.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/ResourceInjectionUtil.java
@@ -55,14 +55,19 @@
   
   /**
    * Installs stylesheets using the installOneStylesheet method, which is
-   * assumed to be defined on the page.
+   * assumed to be defined on the page.  The installOneStylesheet() 
+   * helper function is invoked as follows:
+   * 
+   * <pre>
+   * installOneStylesheet(URL);
+   * </pre>
    */
   public static StringBuffer injectStylesheets(StringBuffer selectionScript,
       ArtifactSet artifacts) {
     int startPos = selectionScript.indexOf("// __MODULE_STYLES__");
     if (startPos != -1) {
       for (StylesheetReference resource : artifacts.find(StylesheetReference.class)) {
-        String text = generateNewStylesheetInjector(resource.getSrc());
+        String text = "installOneStylesheet('" + resource.getSrc() + "');\n";
         selectionScript.insert(startPos, text);
         startPos += text.length();
       }
@@ -70,22 +75,6 @@
     return selectionScript;
   }
   
-  /**
-   * Generate a Snippet of JavaScript to inject an external stylesheet using
-   * the installOneStylesheet helper function (which is assumed to already
-   * be defined on the page.
-   * 
-   * <pre>
-   * installOneStylesheet(URL, HREF_EXPR);
-   * </pre>
-   */
-  private static String generateNewStylesheetInjector(String stylesheetUrl) {
-    String hrefExpr = "'" + stylesheetUrl + "'";
-    if (isRelativeURL(stylesheetUrl)) {
-      hrefExpr = "__MODULE_FUNC__.__moduleBase + " + hrefExpr;
-    }
-    return "installOneStylesheet('" + stylesheetUrl + "', " + hrefExpr + ");\n";
-  }
   
   private static String generateScriptInjector(String scriptUrl) {
     if (isRelativeURL(scriptUrl)) {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/computeUrlForResource.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/computeUrlForResource.js
new file mode 100644
index 0000000..1015257
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/computeUrlForResource.js
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/**
+ * Transform a resource into URL before making a request. This can be overridden
+ * if some sort of proxy mechanism is needed.  This is modeled after the logic 
+ * originally coded in ResourceInjectsionUtils.java
+ */
+function computeUrlForResource(resource) {
+  /* return an absolute path unmodified */
+  if (resource.match(/^\//)) {
+    return resource;
+  }
+  /* return a fully qualified URL unmodified */
+  if (resource.match(/^[a-zA-Z]+:\/\//)) {
+    return resource;
+  }
+  return __MODULE_FUNC__.__moduleBase + resource;
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js
index 7778f3c..534f037 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js
@@ -4,11 +4,11 @@
   // change across GWT versions.
   if (!$wnd.__gwt_stylesLoaded) { $wnd.__gwt_stylesLoaded = {}; }
 
-  function installOneStylesheet(stylesheetUrl, hrefExpr) {
+  function installOneStylesheet(stylesheetUrl) {
     if (!__gwt_stylesLoaded[stylesheetUrl]) {
       var l = $doc.createElement('link');
       l.setAttribute('rel', 'stylesheet');
-      l.setAttribute('href', hrefExpr);
+      l.setAttribute('href', computeUrlForResource(stylesheetUrl));
       $doc.getElementsByTagName('head')[0].appendChild(l);
       __gwt_stylesLoaded[stylesheetUrl] = true;
     }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js
index a36ce80..0d454a0 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js
@@ -42,7 +42,7 @@
   
   sendStats('bootstrap', 'selectingPermutation');
   if (isHostedMode()) {
-    return __MODULE_FUNC__.__moduleBase + "__HOSTED_FILENAME__"; 
+    return computeUrlForResource("__HOSTED_FILENAME__"); 
   }
   var strongName;
   try {
@@ -59,5 +59,5 @@
     // intentionally silent on property failure
   }
   __MODULE_FUNC__.__softPermutationId = softPermutationId;
-  return __MODULE_FUNC__.__moduleBase + strongName + '.cache.js';
+  return computeUrlForResource(strongName + '.cache.js');
 }
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 a15f3bf..68235ea 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -41,7 +41,6 @@
 import java.io.IOException;
 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
@@ -55,31 +54,31 @@
    * 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";
-  
+  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 {
-    
+  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, getJsComputeUrlForResource(context), "__COMPUTE_URL_FOR_RESOURCE__");
     includeJs(ss, logger, getJsLoadExternalStylesheets(context), "__LOAD_STYLESHEETS__");
-    
+
     // This Linker does not support <script> tags in the gwt.xml
     SortedSet<ScriptReference> scripts = artifacts.find(ScriptReference.class);
     if (!scripts.isEmpty()) {
@@ -96,30 +95,29 @@
         }
       }
       if (failIfScriptTags) {
-        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'/>";
+        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'/>";
         logger.log(TreeLogger.ERROR, msg);
         throw new UnableToCompleteException();
       } else {
-        String msg = "Ignoring the following script tags in the gwt.xml " +
-        "file\n" + list;
+        String msg = "Ignoring the following script tags in the gwt.xml " + "file\n" + list;
         logger.log(TreeLogger.INFO, msg);
       }
     }
-    
+
     ss = ResourceInjectionUtil.injectStylesheets(ss, artifacts);
     ss = permutationsUtil.addPermutationsJs(ss, logger, context);
-    
+
     if (result != null) {
-      replaceAll(ss, "__KNOWN_PROPERTIES__",
-          PropertiesUtil.addKnownPropertiesJs(logger, result));
+      replaceAll(ss, "__KNOWN_PROPERTIES__", PropertiesUtil.addKnownPropertiesJs(logger, result));
     }
     replaceAll(ss, "__MODULE_FUNC__", context.getModuleFunctionName());
     replaceAll(ss, "__MODULE_NAME__", context.getModuleName());
@@ -129,24 +127,27 @@
   }
 
   @Override
-  protected String getCompilationExtension(TreeLogger logger,
-      LinkerContext context) {
+  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) { 
+
+  protected String getJsComputeUrlForResource(LinkerContext context) {
+    return "com/google/gwt/core/ext/linker/impl/computeUrlForResource.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) {
@@ -156,32 +157,31 @@
   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) {
+  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; 
+    // 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() + ";");
@@ -197,7 +197,8 @@
     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
+    // 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();
@@ -206,33 +207,29 @@
 
     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,"
+    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) {
+  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 {
+
+  protected void includeJs(StringBuffer selectionScript, TreeLogger logger, String jsSource,
+      String templateVar) throws UnableToCompleteException {
     String js;
     if (jsSource.endsWith(".js")) {
       try {
@@ -248,37 +245,34 @@
   }
 
   @Override
-  protected void maybeAddHostedModeFile(TreeLogger logger, 
-      LinkerContext context, ArtifactSet artifacts, CompilationResult result)
-      throws UnableToCompleteException {
+  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)) {
+    if (isSinglePermutation != shouldIncludeBootstrapInPrimaryFragment(context)) {
       return;
     }
-    
+
     long lastModified = System.currentTimeMillis();
-    StringBuffer buffer = readFileToStringBuffer(
-        "com/google/gwt/core/ext/linker/impl/" + filename, logger); 
+    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);
+    String script =
+        generatePrimaryFragmentString(logger, context, result, buffer.toString(), 1, artifacts);
+
+    EmittedArtifact devArtifact = emitString(logger, script, outputFilename, lastModified);
     artifacts.add(devArtifact);
   }
 
@@ -286,16 +280,15 @@
   @Override
   protected void maybeOutputPropertyMap(TreeLogger logger, LinkerContext context,
       ArtifactSet toReturn) {
-    if (!shouldOutputPropertyMap(context) ||
-        permutationsUtil.getPermutationsMap() == null ||
-        permutationsUtil.getPermutationsMap().isEmpty()) {
+    if (!shouldOutputPropertyMap(context) || permutationsUtil.getPermutationsMap() == null
+        || permutationsUtil.getPermutationsMap().isEmpty()) {
       return;
     }
-    
+
     PropertiesMappingArtifact mappingArtifact =
-      new PropertiesMappingArtifact(CrossSiteIframeLinker.class,
-          permutationsUtil.getPermutationsMap());
-    
+        new PropertiesMappingArtifact(CrossSiteIframeLinker.class, permutationsUtil
+            .getPermutationsMap());
+
     toReturn.add(mappingArtifact);
     EmittedArtifact serializedMap;
     try {
@@ -310,8 +303,8 @@
     }
   }
 
-  // If you set this to return true, you should also override 
-  // getJsPermutations() to return permutationsNull.js and 
+  // 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;
@@ -324,11 +317,10 @@
   protected boolean shouldOutputPropertyMap(LinkerContext context) {
     return false;
   }
-  
+
   @Override
-  protected String wrapPrimaryFragment(TreeLogger logger,
-      LinkerContext context, String script, ArtifactSet artifacts,
-      CompilationResult result) {
+  protected String wrapPrimaryFragment(TreeLogger logger, LinkerContext context, String script,
+      ArtifactSet artifacts, CompilationResult result) {
     StringBuffer out = new StringBuffer();
     if (shouldIncludeBootstrapInPrimaryFragment(context)) {
       try {
@@ -338,7 +330,7 @@
         e.printStackTrace();
       }
     }
-    
+
     if (shouldInstallCode(context)) {
       // Rewrite the code so it can be installed with
       // __MODULE_FUNC__.onScriptDownloaded
@@ -351,5 +343,5 @@
     }
     return out.toString();
   }
-  
+
 }
diff --git a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
index 47a611d..79ed2a4 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
@@ -95,6 +95,9 @@
   // Provides the computeScriptBase() function
   __COMPUTE_SCRIPT_BASE__
 
+  // Provides the computeUrlForResource() function
+  __COMPUTE_URL_FOR_RESOURCE__
+
   // Provides the getCompiledCodeFilename() function which sets the 
   // __gwt_isKnownPropertyValue, MODULE_FUNC__.__computePropValue and
   // __MODULE_FUNC__.__softPermutationId variables if needed