Fix for issue #1022; makes GWT.getModuleBaseURL() correct in the mashup case.  Some cases require crazy magic with setting and retrieving the URL of an image.  Also adds a new GWT.getHostPageBaseURL().

Found by: me
Suggested by: jason.essington
Review by: bruce, jgw, mmendez

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1052 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index efa9e75..0d63765 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -454,7 +454,7 @@
     out.newlineOpt();
     out.print("var $doc = $wnd.document;");
     out.newlineOpt();
-    out.print("var $moduleName = \"" + moduleName + "\";");
+    out.print("var $moduleName, $moduleBase;");
     out.newlineOpt();
     out.print("</script></head>");
     out.newlineOpt();
@@ -500,11 +500,11 @@
 
     // Setup the well-known variables.
     //
-    out.print("var $wnd = parent;");
+    out.print("var $wnd = window;");
     out.newlineOpt();
     out.print("var $doc = $wnd.document;");
     out.newlineOpt();
-    out.print("var $moduleName = \"" + moduleName + "\";");
+    out.print("var $moduleName, $moduleBase;");
     out.newlineOpt();
 
     return out.toString();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 66d473c..eff99d2 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -1273,7 +1273,9 @@
     private void generateGwtOnLoad(List entryFuncs, JsStatements globalStmts) {
       /**
        * <pre>
-       * function gwtOnLoad(errFn, modName){
+       * function gwtOnLoad(errFn, modName, modBase){
+       *   $moduleName = modName;
+       *   $moduleBase = modBase;
        *   if (errFn) {
        *     try {
        *       init();
@@ -1297,8 +1299,17 @@
       JsParameters params = gwtOnLoad.getParameters();
       JsName errFn = fnScope.declareName("errFn");
       JsName modName = fnScope.declareName("modName");
+      JsName modBase = fnScope.declareName("modBase");
       params.add(new JsParameter(errFn));
       params.add(new JsParameter(modName));
+      params.add(new JsParameter(modBase));
+      JsExpression asg = createAssignment(
+          topScope.findExistingUnobfuscatableName("$moduleName").makeRef(),
+          modName.makeRef());
+      body.getStatements().add(asg.makeStmt());
+      asg = createAssignment(topScope.findExistingUnobfuscatableName(
+          "$moduleBase").makeRef(), modBase.makeRef());
+      body.getStatements().add(asg.makeStmt());
       JsIf jsIf = new JsIf();
       body.getStatements().add(jsIf);
       jsIf.setIfExpr(errFn.makeRef());
@@ -1493,7 +1504,8 @@
       }
 
       JReferenceType enclosingType = x.getEnclosingType();
-      if (!typeOracle.checkClinit(currentMethod.getEnclosingType(), enclosingType)) {
+      if (!typeOracle.checkClinit(currentMethod.getEnclosingType(),
+          enclosingType)) {
         return null;
       }
 
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
index 421f3c9..05129b2 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
@@ -46,7 +46,7 @@
         "Error", "Function", "Global", "Image", "Math", "Number", "Object",
         "RegExp", "String", "VBArray", "window", "document", "event",
         "arguments", "call", "toString", "$wnd", "$doc", "$moduleName",
-        "debugger", "undefined"};
+        "$moduleBase", "debugger", "undefined"};
 
     for (int i = 0; i < commonBuiltins.length; i++) {
       String ident = commonBuiltins[i];
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeSourceOracle.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeSourceOracle.java
index 10d0a77..9947b5e 100644
--- a/dev/core/src/com/google/gwt/dev/shell/HostedModeSourceOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedModeSourceOracle.java
@@ -21,6 +21,9 @@
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.jdt.StandardSourceOracle;
 import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.IOException;
 
 /**
  * Does a little extra magic to handle hosted mode JSNI and
@@ -28,102 +31,10 @@
  */
 public class HostedModeSourceOracle extends StandardSourceOracle {
 
-  private final CompilationUnitProvider cuMeta = new StaticCompilationUnitProvider(
-      "com.google.gwt.core.client", "GWT", null) {
-    public char[] getSource() {
-      StringBuffer sb = new StringBuffer();
-      sb.append("package com.google.gwt.core.client;\n");
-      sb.append("public final class GWT {\n");
-
-      // UncaughtExceptionHandler
-      //
-      sb.append("  public interface UncaughtExceptionHandler {\n");
-      sb.append("    void onUncaughtException(Throwable e);\n");
-      sb.append("  }\n");
-
-      sb.append("  private static String sModuleBaseURL = null;\n");
-
-      // Hosted mode default to logging
-      //
-      sb.append("  private static UncaughtExceptionHandler sUncaughtExceptionHandler = \n");
-      sb.append("    new UncaughtExceptionHandler() {\n");
-      sb.append("      public void onUncaughtException(Throwable e) {\n");
-      sb.append("        log(\"Uncaught exception escaped\", e);\n");
-      sb.append("      }\n");
-      sb.append("    };\n");
-
-      // Implement getUncaughtExceptionHandler()
-      //
-      sb.append("  public static UncaughtExceptionHandler getUncaughtExceptionHandler() {\n");
-      sb.append("    return sUncaughtExceptionHandler;\n");
-      sb.append("  }\n");
-
-      // Implement setUncaughtExceptionHandler()
-      //
-      sb.append("  public static void setUncaughtExceptionHandler(\n");
-      sb.append("      UncaughtExceptionHandler handler) {\n");
-      sb.append("    sUncaughtExceptionHandler = handler;\n");
-      sb.append("  }\n");
-
-      // Proxy create().
-      //
-      sb.append("  public static Object create(Class classLiteral) {\n");
-      sb.append("    return ");
-      sb.append(ShellGWT.class.getName());
-      sb.append(".create(classLiteral);\n");
-      sb.append("  }\n");
-
-      // Proxy getTypeName().
-      //
-      sb.append("  public static String getTypeName(Object o) {\n");
-      sb.append("    return ");
-      sb.append(ShellGWT.class.getName());
-      sb.append(".getTypeName(o);");
-      sb.append("  }\n");
-
-      // Hard-code isScript() to false.
-      //
-      sb.append("  public static boolean isScript() {\n");
-      sb.append("    return false;");
-      sb.append("  }\n");
-
-      // Actually, we don't need to proxy getModuleName().
-      // It's hard-coded.
-      //
-      sb.append("  public static String getModuleName() {\n");
-      sb.append("    return \"");
-      sb.append(moduleName);
-      sb.append("\";\n");
-      sb.append("  }\n");
-
-      // Proxy getModuleBaseURL() to the Impl class.
-      //
-      sb.append("  public static String getModuleBaseURL() {\n");
-      sb.append("    if (sModuleBaseURL == null) {\n");
-      sb.append("      sModuleBaseURL = Impl.getModuleBaseURL();\n");
-      sb.append("    }\n");
-      sb.append("    return sModuleBaseURL;\n");
-      sb.append("  }\n");
-
-      // Proxy log().
-      //
-      sb.append("  public static void log(String message, Throwable e) {\n  ");
-      sb.append(ShellGWT.class.getName());
-      sb.append(".log(message, e);\n");
-      sb.append("  }\n");
-
-      sb.append("}\n");
-      return sb.toString().toCharArray();
-    }
-  };
-
   private final JsniInjector injector;
 
-  private final String moduleName;
-
-  public HostedModeSourceOracle(TypeOracle typeOracle, String moduleName) {
+  public HostedModeSourceOracle(TypeOracle typeOracle) {
     super(typeOracle);
-    this.moduleName = moduleName;
     this.injector = new JsniInjector(typeOracle);
   }
 
@@ -131,25 +42,29 @@
       String typeName, CompilationUnitProvider existing)
       throws UnableToCompleteException {
 
-    // MAGIC: The implementation of GWT.create() is handled intrinsically by
-    // the compiler in web mode, so its on-disk definition is totally empty so
-    // as to be trivially compilable. In hosted mode, GWT.create() is
-    // actually a real call, so we patch different source for that class in
-    // hosted mode only.
-    //
-    // MAGIC: The implementation of GWT.getTypeSignature() is handled
-    // differently in web mode versus hosted mode. The on-disk version is
-    // the web mode version, so here we substitute the hosted mode version.
-    //
+    /*
+     * MAGIC: The implementation of GWT can be very different between hosted
+     * mode and web mode. The compiler has special knowledge of GWT for web
+     * mode. The source for hosted mode is in GWT.java-hosted.
+     */
     if (typeName.equals("com.google.gwt.core.client.GWT")) {
-      return cuMeta;
+      try {
+        String source = Utility.getFileFromClassPath("com/google/gwt/core/client/GWT.java-hosted");
+        return new StaticCompilationUnitProvider("com.google.gwt.core.client",
+            "GWT", source.toCharArray());
+      } catch (IOException e) {
+        logger.log(
+            TreeLogger.ERROR,
+            "Unable to load 'com/google/gwt/core/client/GWT.java-hosted' from class path; is your installation corrupt?",
+            e);
+        throw new UnableToCompleteException();
+      }
     }
 
     // Otherwise, it's a regular translatable type, but we want to make sure
     // its JSNI stuff, if any, gets handled.
     //
     CompilationUnitProvider jsnified = injector.inject(logger, existing);
-
     return jsnified;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
index 111efca..a107f79 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
@@ -89,8 +89,7 @@
     // Create a host for the hosted mode compiler.
     // We add compilation units to it as deferred binding generators write them.
     //
-    SourceOracle srcOracle = new HostedModeSourceOracle(typeOracle,
-        module.getName());
+    SourceOracle srcOracle = new HostedModeSourceOracle(typeOracle);
 
     // Create or find the compiler to be used by the compiling class loader.
     //
diff --git a/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate-xs.js b/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate-xs.js
index fca59b1..d7bc6f2 100644
--- a/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate-xs.js
+++ b/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate-xs.js
@@ -79,7 +79,7 @@
   function maybeStartModule() {
     // TODO: it may not be necessary to check gwtOnLoad here.
     if (gwtOnLoad && bodyDone) {
-      gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__');
+      gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base);
     }
   }
 
@@ -97,17 +97,32 @@
       thisScript = markerScript.previousSibling;
     }
 
-    if (thisScript) {
+    function getDirectoryOfFile(path) {
+      var eq = path.lastIndexOf('/');
+      return (eq >= 0) ? path.substring(0, eq + 1) : '';
+    };
+
+    if (thisScript && thisScript.src) {
       // Compute our base url
-      var content = thisScript.src;
-      if (content) {
-        var eq = content.lastIndexOf('/');
-        if (eq >= 0) {
-          base = content.substring(0, eq + 1);
-        }
-      }
+      base = getDirectoryOfFile(thisScript.src);
     }
     
+    // Make the base URL absolute
+    if (base == '') {
+      // Trivial case; the base must be the same as the document location
+      base = getDirectoryOfFile($doc.location.href);
+    } else if ((base.match(/^\w+:\/\//))) {
+      // If the URL is obviously absolute, do nothing.
+    } else {
+      // Probably a relative URL; use magic to make the browser absolutify it.
+      // I wish there were a better way to do this, but this seems the only
+      // 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);
+    }
+
     if (markerScript) {
       // remove the marker element
       markerScript.parentNode.removeChild(markerScript);
diff --git a/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate.js b/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate.js
index 637487f..a2eed87 100644
--- a/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate.js
+++ b/dev/core/src/com/google/gwt/dev/util/SelectionScriptTemplate.js
@@ -90,7 +90,7 @@
       }
       // remove this whole function from the global namespace to allow GC
       __MODULE_FUNC__ = null;
-      frameWnd.gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__');
+      frameWnd.gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base);
     }
   }
   
@@ -114,15 +114,30 @@
       }
     }
 
-    if (thisScript) {
+    function getDirectoryOfFile(path) {
+      var eq = path.lastIndexOf('/');
+      return (eq >= 0) ? path.substring(0, eq + 1) : '';
+    };
+
+    if (thisScript && thisScript.src) {
       // Compute our base url
-      var content = thisScript.src;
-      if (content) {
-        var eq = content.lastIndexOf('/');
-        if (eq >= 0) {
-          base = content.substring(0, eq + 1);
-        }
-      }
+      base = getDirectoryOfFile(thisScript.src);
+    }
+    
+    // Make the base URL absolute
+    if (base == '') {
+      // Trivial case; the base must be the same as the document location
+      base = getDirectoryOfFile($doc.location.href);
+    } else if ((base.match(/^\w+:\/\//))) {
+      // If the URL is obviously absolute, do nothing.
+    } else {
+      // Probably a relative URL; use magic to make the browser absolutify it.
+      // I wish there were a better way to do this, but this seems the only
+      // 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);
     }
     
     if (markerScript) {
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index 2fa0075..ecc3739 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -21,6 +21,10 @@
  * deferred binding.
  */
 public final class GWT {
+  /*
+   * This is the web mode version of this class. Because it's so special,
+   * there's also a hosted mode version.  See GWT.java-hosted.
+   */
 
   /**
    * This interface is used to catch exceptions at the "top level" just before
@@ -35,11 +39,7 @@
     void onUncaughtException(Throwable e);
   }
 
-  // cache of the module base URL
-  private static String sModuleBaseURL = null;
-
   // web mode default is to let the exception go
-  // hosted mode default is to log the exception to the log window
   private static UncaughtExceptionHandler sUncaughtExceptionHandler = null;
 
   /**
@@ -58,10 +58,6 @@
    */
   public static Object create(Class classLiteral) {
     /*
-     * In hosted mode, this whole class definition is replaced at runtime with
-     * an implementation defined by the hosting environment. Maintainers: see
-     * HostedModeSourceOracle.cuMeta}.
-     * 
      * In web mode, the compiler directly replaces calls to this method with a
      * new Object() type expression of the correct rebound type.
      */
@@ -70,6 +66,18 @@
   }
 
   /**
+   * Gets the URL prefix of the hosting page, useful for prepending to relative
+   * paths of resources which may be relative to the host page. Typically, you
+   * should use {@link #getModuleBaseURL()} unless you have a specific reason to
+   * load a resource relative to the host page.
+   * 
+   * @return if non-empty, the base URL is guaranteed to end with a slash
+   */
+  public static String getHostPageBaseURL() {
+    return Impl.getHostPageBaseURL();
+  }
+
+  /**
    * Gets the URL prefix of the module which should be prepended to URLs that
    * are intended to be module-relative, such as RPC entry points and files in
    * the module's public path.
@@ -77,18 +85,15 @@
    * @return if non-empty, the base URL is guaranteed to end with a slash
    */
   public static String getModuleBaseURL() {
-    if (sModuleBaseURL == null) {
-      sModuleBaseURL = Impl.getModuleBaseURL();
-    }
-    return sModuleBaseURL;
+    return Impl.getModuleBaseURL();
   }
 
   /**
    * Gets the name of the running module.
    */
-  public static native String getModuleName() /*-{
-   return $moduleName;
-   }-*/;
+  public static String getModuleName() {
+    return Impl.getModuleName();
+  }
 
   /**
    * Gets the class name of the specified object, as would be returned by
diff --git a/user/src/com/google/gwt/core/client/GWT.java-hosted b/user/src/com/google/gwt/core/client/GWT.java-hosted
new file mode 100644
index 0000000..1b4c22d
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/GWT.java-hosted
@@ -0,0 +1,76 @@
+/*

+ * Copyright 2007 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.client;

+

+import com.google.gwt.dev.shell.ShellGWT;

+

+/**

+ * The hosted mode implementation of the magic GWT class, with different

+ * implementations of certain core methods.

+ */

+public final class GWT {

+  public interface UncaughtExceptionHandler {

+    void onUncaughtException(Throwable e);

+  }

+

+  // hosted mode default is to log the exception to the log window

+  private static UncaughtExceptionHandler sUncaughtExceptionHandler = new UncaughtExceptionHandler() {

+    public void onUncaughtException(Throwable e) {

+      log("Uncaught exception escaped", e);

+    }

+  };

+

+  public static UncaughtExceptionHandler getUncaughtExceptionHandler() {

+    return sUncaughtExceptionHandler;

+  }

+

+  public static void setUncaughtExceptionHandler(

+      UncaughtExceptionHandler handler) {

+    sUncaughtExceptionHandler = handler;

+  }

+

+  public static Object create(Class classLiteral) {

+    // deferred binding at runtime

+    return ShellGWT.create(classLiteral);

+  }

+

+  public static String getTypeName(Object o) {

+    // uses reflection in hosted mode

+    return ShellGWT.getTypeName(o);

+  }

+

+  public static boolean isScript() {

+    // false in hosted mode

+    return false;

+  }

+

+  public static String getHostPageBaseURL() {

+    return Impl.getHostPageBaseURL();

+  }

+

+  public static String getModuleBaseURL() {

+    return Impl.getModuleBaseURL();

+  }

+

+  public static String getModuleName() {

+    return Impl.getModuleName();

+  }

+

+  public static void log(String message, Throwable e) {

+    // logs to the shell logger in hosted mode

+    ShellGWT.log(message, e);

+  }

+}

diff --git a/user/src/com/google/gwt/core/client/Impl.java b/user/src/com/google/gwt/core/client/Impl.java
index 0dad830..cb37219 100644
--- a/user/src/com/google/gwt/core/client/Impl.java
+++ b/user/src/com/google/gwt/core/client/Impl.java
@@ -38,9 +38,8 @@
      (o.$H ? o.$H : (o.$H = @com.google.gwt.core.client.Impl::getNextHashId()()));
   }-*/;
 
-  static native String getModuleBaseURL() /*-{
-    // this is intentionally not using $doc, because we want the module's own url
-    var s = document.location.href;
+  static native String getHostPageBaseURL() /*-{
+    var s = $doc.location.href;
 
     // Pull off any hash.
     var i = s.indexOf('#');
@@ -60,4 +59,12 @@
     // Ensure a final slash if non-empty.
     return s.length > 0 ? s + "/" : "";
   }-*/;
+
+  static native String getModuleBaseURL() /*-{
+    return $moduleBase;
+  }-*/;
+
+  static native String getModuleName() /*-{
+    return $moduleName;
+  }-*/;
 }
diff --git a/user/src/com/google/gwt/core/public/hosted.html b/user/src/com/google/gwt/core/public/hosted.html
index 9aeacec..e2cd669 100644
--- a/user/src/com/google/gwt/core/public/hosted.html
+++ b/user/src/com/google/gwt/core/public/hosted.html
@@ -2,13 +2,14 @@
 <head><script>
 var $wnd = parent;
 var $doc = $wnd.document;
-var $moduleName;
+var $moduleName, $moduleBase;
 </script></head>
 <body>
 <font face='arial' size='-1'>This html file is for hosted mode support.</font>
 <script><!--
-function gwtOnLoad(errFn, modName){
+function gwtOnLoad(errFn, modName, modBase){
   $moduleName = modName;
+  $moduleBase = modBase;
   if (!external.gwtOnLoad(window, modName)) {
     if (errFn) {
       errFn(modName);