Fix some issues in the xsiframe linker
- Add support for including <stylesheet> tags
- Fix a bug in computeScriptBase.js

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

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9158 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 0986ee4..4691444 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
@@ -27,6 +27,9 @@
  * Utility class to help linkers do resource injection.
  */
 public class ResourceInjectionUtil {
+  /**
+   * Installs stylesheets and scripts
+   */
   public static StringBuffer injectResources(StringBuffer selectionScript,
       ArtifactSet artifacts) {
     // Add external dependencies
@@ -50,6 +53,40 @@
     return selectionScript;
   }
   
+  /**
+   * Installs stylesheets using the installOneStylesheet method, which is
+   * assumed to be defined on the page.
+   */
+  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());
+        selectionScript.insert(startPos, text);
+        startPos += text.length();
+      }
+    }
+    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)) {
       return "  if (!__gwt_scriptsLoaded['"
@@ -67,7 +104,7 @@
           + scriptUrl + "\\\"></script>');\n" + "  }\n";
     }
   }
-  
+
   /**
    * Generate a Snippet of JavaScript to inject an external stylesheet.
    * 
@@ -95,7 +132,7 @@
         + "  $doc.getElementsByTagName('head')[0].appendChild(l);\n         "
         + "}\n";
   }
-  
+
   /**
    * Determines whether or not the URL is relative.
    * 
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js
index 066ce99..dd27fa0 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js
@@ -15,11 +15,13 @@
  */
 
 /**
- * Determine our own script's URL via magic :)
- * This function produces one side-effect, it sets base to the module's
- * base url. Note: although this script returns the module's base url, it
- * also sets the global 'base' variable for backwards compatability with older
- * linkers.
+ * Determine our own script's URL by trying various things
+ *
+ * First - use the baseUrl meta tag if it exists
+ * Second - look for a script tag with the src set to MODULE_NAME.nocache.js and
+ *   if it's found, use it to determine the baseUrl
+ * Third - if the page is not already loaded, try to use some document.write
+ *   magic to install a temporary tag and use that to determine the baseUrl.
  * 
  * This is included into the selection scripts
  * wherever COMPUTE_SCRIPT_BASE appears with underlines
@@ -32,10 +34,15 @@
   // Note: the base variable should not be defined in this function because in
   // the older templates (like IFrameTemplate.js), base is defined outside this
   // function, and they rely on the fact that calling computeScriptBase will set
-  // that base variable rather than using the return value.
+  // that base variable rather than using the return value. Instead, we define
+  // a tempBase variable, and then right before we return, we also set the
+  // base variable for backwards compatability
+  var tempBase = '';
+
   if (metaVal != null) {
-    base = metaVal;
-    return base;
+    tempBase = metaVal;
+    base = tempBase;
+    return tempBase;
   }
 
   // The baseUrl will be similar to the URL for this script's URL
@@ -54,16 +61,18 @@
   // it. Note that this will not work in the Late Loading case due to the
   // document.write call.
   if (!thisScript) {
-    // Put in a marker script element which should be the first script tag after
-    // the tag we're looking for. To find it, we start at the marker and walk
-    // backwards until we find a script.
-    var markerId = "__gwt_marker___MODULE_NAME__";
-    var markerScript;
-    $doc.write('<script id="' + markerId + '"></script>');
-    markerScript = $doc.getElementById(markerId);
-    thisScript = markerScript && markerScript.previousSibling;
-    while (thisScript && thisScript.tagName != 'SCRIPT') {
-      thisScript = thisScript.previousSibling;
+    if (typeof isBodyLoaded == 'undefined' || !isBodyLoaded()) {
+      // Put in a marker script element which should be the first script tag after
+      // the tag we're looking for. To find it, we start at the marker and walk
+      // backwards until we find a script.
+      var markerId = "__gwt_marker___MODULE_NAME__";
+      var markerScript;
+      $doc.write('<script id="' + markerId + '"></script>');
+      markerScript = $doc.getElementById(markerId);
+      thisScript = markerScript && markerScript.previousSibling;
+      while (thisScript && thisScript.tagName != 'SCRIPT') {
+        thisScript = thisScript.previousSibling;
+      }
     }
   }
 
@@ -84,21 +93,21 @@
 
   if (thisScript && thisScript.src) {
     // Compute our base url
-    base = getDirectoryOfFile(thisScript.src);
+    tempBase = getDirectoryOfFile(thisScript.src);
   }
 
   // Make the base URL absolute
-  if (base == '') {
+  if (tempBase == '') {
     // If there's a base tag, use it.
     var baseElements = $doc.getElementsByTagName('base');
     if (baseElements.length > 0) {
       // It's always the last parsed base tag that will apply to this script.
-      base = baseElements[baseElements.length - 1].href;
+      tempBase = baseElements[baseElements.length - 1].href;
     } else {
       // No base tag; the base must be the same as the document location.
-      base = getDirectoryOfFile($doc.location.href);
+      tempBase = getDirectoryOfFile($doc.location.href);
     }
-  } else if ((base.match(/^\w+:\/\//))) {
+  } else if ((tempBase.match(/^\w+:\/\//))) {
     // If the URL is obviously absolute, do nothing.
   } else {
     // Probably a relative URL; use magic to make the browser absolutify it.
@@ -106,8 +115,8 @@
     // sure way!  (A side benefit is it preloads clear.cache.gif)
     // Note: this trick is harmless if the URL was really already absolute.
     var img = $doc.createElement("img");
-    img.src = base + 'clear.cache.gif';
-    base = getDirectoryOfFile(img.src);
+    img.src = tempBase + 'clear.cache.gif';
+    tempBase = getDirectoryOfFile(img.src);
   }
 
   if (markerScript) {
@@ -115,5 +124,6 @@
     markerScript.parentNode.removeChild(markerScript);
   }
 
-  return base;
-}
\ No newline at end of file
+  base = tempBase;
+  return tempBase;
+}
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 43d4b0a..0e3d90f 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
@@ -1,8 +1,13 @@
+// Installs a script which has already been downloaded (usually because the 
+// script contents are combined with the bootstrap script in cases like SSSS).
+// Since the script contents are wrapped in a call to onScriptDownloaded, all
+// we do here is set up that function, which will install the contents in
+// a script tag appended to the install location.
 function installScript(filename) {
   // Provides the getInstallLocation() and getInstallLocationDoc() functions
   __INSTALL_LOCATION__
 
-  // Provides the setupWaitForBodyLoad() and isBodyLoaded() functions
+  // Provides the setupWaitForBodyLoad() function
   __WAIT_FOR_BODY_LOADED__
 
   function installCode(code) {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptDirect.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptDirect.js
index 16c19aa..e843e7b 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptDirect.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptDirect.js
@@ -1,8 +1,10 @@
+// Installs the script directly, by simply appending a script tag with the
+// src set to the correct location to the install location.
 function installScript(filename) {
   // Provides the getInstallLocation() and getInstallLocationDoc() functions
   __INSTALL_LOCATION__
 
-  // Provides the setupWaitForBodyLoad() and isBodyLoaded() functions
+  // Provides the setupWaitForBodyLoad()function
   __WAIT_FOR_BODY_LOADED__
   
   function installCode(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 877e4e2..a9aa739 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
@@ -1,8 +1,12 @@
+// Installs the script by immediately appending a script tag to the body head
+// with the src set, to get the script contents. The script contents are then
+// installed into a script tag which is added to the install location (because
+// the script contents will be wrapped in a call to onScriptDownloaded()).
 function installScript(filename) {
   // Provides the getInstallLocation() and getInstallLocationDoc() functions
   __INSTALL_LOCATION__
 
-  // Provides the setupWaitForBodyLoad() and isBodyLoaded() functions
+  // Provides the setupWaitForBodyLoad() function
   __WAIT_FOR_BODY_LOADED__
 
   function installCode(code) {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptNull.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptNull.js
index e3787b9..382e34a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptNull.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptNull.js
@@ -1,3 +1,5 @@
+// Assumes that the script contents are directly part of the bootstrap script
+// and do not need to be installed anywhere. 
 function installScript(filename) {
   // Does nothing
 }
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
new file mode 100644
index 0000000..8935924
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/loadExternalStylesheets.js
@@ -0,0 +1,24 @@
+function loadExternalStylesheets() {
+  // Setup for loading of external stylesheets. Resources are loaded
+  // only once, even when multiple modules depend on them.  This API must not
+  // change across GWT versions.
+  if (!$wnd.__gwt_stylesLoaded) { $wnd.__gwt_stylesLoaded = {}; }
+
+  function installOneStylesheet(stylesheetUrl, hrefExpr) {
+    if (!__gwt_stylesLoaded[stylesheetUrl]) {
+      if (isBodyLoaded()) {
+        var l = $doc.createElement('link');
+        l.setAttribute('rel', 'stylesheet');
+        l.setAttribute('href', hrefExpr);
+        $doc.getElementsByTagName('head')[0].appendChild(l);
+      } else {
+        $doc.write("<link id='' rel='stylesheet' href='" + hrefExpr + "'></li" + "nk>");
+      }
+      __gwt_stylesLoaded[stylesheetUrl] = true;
+    }
+  }
+
+  sendStats('loadExternalRefs', 'begin');
+  // __MODULE_STYLES__
+  sendStats('loadExternalRefs', 'end');
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js
index 839a68d..35ed95d 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js
@@ -1,8 +1,3 @@
-// Check whether the body is loaded.
-function isBodyLoaded() {
-  return (/loaded|complete/.test($doc.readyState));
-}
-
 // Setup code which waits for the body to be loaded and then calls the
 // callback function
 function setupWaitForBodyLoad(callback) {
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 23d8b9f..8ea1b9e 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -25,6 +25,7 @@
 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.PropertiesMappingArtifact;
+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;
@@ -65,13 +66,15 @@
     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__");
     
+    ss = ResourceInjectionUtil.injectStylesheets(ss, artifacts);
+    ss = permutationsUtil.addPermutationsJs(ss, logger, context);
+
     replaceAll(ss, "__MODULE_FUNC__", context.getModuleFunctionName());
     replaceAll(ss, "__MODULE_NAME__", context.getModuleName());
     replaceAll(ss, "__HOSTED_FILENAME__", getHostedFilename());
 
-    permutationsUtil.addPermutationsJs(ss, logger, context);
-
     return ss.toString();
   }
 
@@ -99,6 +102,10 @@
   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";
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 5b458ca..fe7c0dc 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
@@ -22,6 +22,10 @@
    * Internal Helper Functions
    ***************************************************************************/
 
+  function isBodyLoaded() {
+    return (/loaded|complete/.test($doc.readyState));
+  }
+
   function isHostedMode() {
     var query = $wnd.location.search;
     return (query.indexOf('gwt.codesvr=') != -1);
@@ -90,6 +94,8 @@
   // __MODULE_FUNC__.__softPermutationId variables if needed
   __PERMUTATIONS__
 
+  // Provides the loadExternalStylesheets() function
+  __LOAD_STYLESHEETS__
 
   /****************************************************************************
    * Bootstrap startup code
@@ -103,11 +109,9 @@
   // Must be done right before the "bootstrap" "end" stat is sent
   var filename = getCompiledCodeFilename();
 
+  loadExternalStylesheets();
+
   sendStats('bootstrap', 'end');
-  // For now, send this dummy statistic since some people are depending on it
-  // being present. TODO(unnurg): remove this statistic soon
-  sendStats('loadExternalRefs', 'begin');
-  sendStats('loadExternalRefs', 'end');
 
   installScript(filename);