Fixes issue #282; adds a DOM.setImgSrc() in order to use crazy magic to avoid multiple image fetches when the same image is requested multiple times simultaneously on IE.

Accidentally, this commit also contains a fix to HistoryImplSafari to load history.html relative to the module base url, which was reviewed by jgw.

Found by: cchenshong2001
Patch by: scottb, tobyr, bruce
Review by: bruce, jgw, jat, tobyr


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1051 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/DOM.java b/user/src/com/google/gwt/user/client/DOM.java
index d9a0bae..cdbb821 100644
--- a/user/src/com/google/gwt/user/client/DOM.java
+++ b/user/src/com/google/gwt/user/client/DOM.java
@@ -733,6 +733,18 @@
   }
 
   /**
+   * Gets the src attribute of an img element. This method is paired with
+   * {@link #setImgSrc(Element, String)} so that it always returns the correct
+   * url.
+   *
+   * @param img a non-null img whose src attribute is to be read.
+   * @return the src url of the img
+   */
+  public static String getImgSrc(Element img) {
+    return impl.getImgSrc(img);
+  }
+
+  /**
    * Gets an HTML representation of an element's children.
    *
    * @param elem the element whose HTML is to be retrieved
@@ -907,6 +919,7 @@
     // receive events.
     sEventPreviewStack.remove(preview);
   }
+
   /**
    * Scrolls the given element into view.
    *
@@ -1021,6 +1034,17 @@
   }
 
   /**
+   * Sets the src attribute of an img element. This method ensures that imgs
+   * only ever have their contents requested one single time from the server.
+   *
+   * @param img a non-null img whose src attribute will be set.
+   * @param src a non-null url for the img
+   */
+  public static void setImgSrc(Element img, String src) {
+    impl.setImgSrc(img, src);
+  }
+
+  /**
    * Sets the HTML contained within an element.
    *
    * @param elem the element whose inner HTML is to be set
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImpl.java b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
index 6c05f07..a1831ba 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
@@ -210,6 +210,10 @@
 
   public abstract Element getFirstChild(Element elem);
 
+  public native String getImgSrc(Element img) /*-{
+    return img.src;
+  }-*/;
+
   public native String getInnerHTML(Element elem) /*-{
     var ret = elem.innerHTML;
     return (ret == null) ? null : ret;
@@ -349,6 +353,10 @@
     elem.__listener = listener;
   }-*/;
 
+  public native void setImgSrc(Element img, String src) /*-{
+    img.src = src;
+  }-*/;
+
   public native void setInnerHTML(Element elem, String html) /*-{
     if (!html) {
       html = '';
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java b/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java
index c1adddc..739e1fe 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplIE6.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.user.client.impl;
 
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 
@@ -24,6 +25,14 @@
  */
 class DOMImplIE6 extends DOMImpl {
 
+  /**
+   * A native map of image source URL strings to Image objects. All Image
+   * objects with values in this map are waiting on an asynchronous load to
+   * complete and have event handlers hooked. The moment the image finishes
+   * loading, it will be removed from this map.
+   */
+  JavaScriptObject srcImgMap;
+
   public native boolean compare(Element elem1, Element elem2) /*-{
     if (!elem1 && !elem2)
       return true;
@@ -116,6 +125,14 @@
     return child || null;
   }-*/;
 
+  /*
+   * The src may not be set yet because of funky logic in setImgSrc(). See
+   * setImgSrc().
+   */
+  public native String getImgSrc(Element img) /*-{
+    return img.__targetSrc || img.src;
+  }-*/;
+
   public native String getInnerText(Element elem) /*-{
     var ret = elem.innerText;
     return (ret == null) ? null : ret;
@@ -136,6 +153,18 @@
   }-*/;
 
   public native void init() /*-{
+    // Fix IE background image refresh bug, present through IE6
+    // see http://www.mister-pixel.com/#Content__state=is_that_simple
+    // this only works with IE6 SP1+
+    try {
+      $doc.execCommand("BackgroundImageCache", false, true);
+    } catch (e) {
+      // ignore error on other browsers
+    }
+  
+    // Initialize the URL -> Image map.
+    this.@com.google.gwt.user.client.impl.DOMImplIE6::srcImgMap = {};
+
     // Set up event dispatchers.
     $wnd.__dispatchEvent = function() {
       if ($wnd.event.returnValue == null) {
@@ -200,12 +229,84 @@
 
   public native void releaseCapture(Element elem) /*-{
     elem.releaseCapture();
-  }-*/;
-
+  }-*/; 
+  
   public native void setCapture(Element elem) /*-{
     elem.setCapture();
   }-*/;
 
+  /*
+   * Works around an IE problem where multiple images trying to load at the same
+   * time will generate a request per image. We fix this by only allowing the
+   * first image of a given URL to set its source immediately, but simultaneous
+   * requests for the same URL don't actually get their source set until the
+   * original load is complete.
+   * 
+   * See comment on srcImgMap for the invariants that hold for the synthetic
+   * Image objects which are values in the map.
+   */
+  public native void setImgSrc(Element img, String src) /*-{
+    // Grab the URL -> Image map.
+    var map = this.@com.google.gwt.user.client.impl.DOMImplIE6::srcImgMap;
+    
+    // See if there's already an image for this URL in the map (e.g. loading).
+    var queuedImg = map[src];
+    if (queuedImg) {
+      // just add myself to its array, it will set my source later
+      queuedImg.__kids.push(img);
+      // record the desired src so it can be retreived synchronously
+      img.__targetSrc = src;
+      return; 
+    }
+
+    // No outstanding requests; load the image.
+    img.src = src;
+
+    // If the image was in cache, the load may have just happened synchronously.
+    if (img.complete) {
+      // We're done
+      return;
+    }
+
+    // Image is loading asynchronously; put in map for chaining.
+    var kids = img.__kids = [];
+    map[src] = img;
+
+    // Store all the old handlers
+    var _onload = img.onload, _onerror = img.onerror, _onabort = img.onabort;
+
+    // Same cleanup code matter what state we end up in.
+    img.onload = function() {
+      finish("onload");
+    }
+    img.onerror = function() {
+      finish("onerror");
+    }
+    img.onabort = function() {
+      finish("onabort");
+    }
+
+    function finish(whichEvent) {
+      // Clean up: restore event handlers and remove from map.
+      img.onload = _onload; img.onerror = _onerror; img.onabort = _onabort;
+      delete map[src];
+      
+      // Set the source for all kids in a timer to ensure caching has happened.
+      window.setTimeout(function() {
+        for (var i = 0; i < kids.length; ++i) {
+          kids[i].src = img.src;
+          // clear the target src now that it's resolved
+          kids[i].__targetSrc = null;
+        }
+      }, 0);      
+      
+      // call the original handler, if any
+      if (img[whichEvent]) {
+        img[whichEvent]();
+      }
+    }
+  }-*/;
+
   public native void setInnerText(Element elem, String text) /*-{
     if (!text)
       text = '';
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java b/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
index 9f8e63f..b5d4139 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
@@ -44,7 +44,8 @@
   
   protected native void newItemImpl(Element historyFrame, String historyToken) /*-{
     historyToken = historyToken || "";
-    historyFrame.contentWindow.location.href = 'history.html?' + historyToken;
+    var base = @com.google.gwt.core.client.GWT::getModuleBaseURL()();
+    historyFrame.contentWindow.location.href = base + 'history.html?' + historyToken;
   }-*/;
 
 }
diff --git a/user/src/com/google/gwt/user/client/ui/Image.java b/user/src/com/google/gwt/user/client/ui/Image.java
index e0752af..dcabc0e 100644
--- a/user/src/com/google/gwt/user/client/ui/Image.java
+++ b/user/src/com/google/gwt/user/client/ui/Image.java
@@ -122,7 +122,7 @@
     }
 
     public String getUrl(Image image) {
-      return DOM.getElementProperty(image.getElement(), "src");
+      return DOM.getImgSrc(image.getElement());
     }
 
     public int getWidth(Image image) {
@@ -130,7 +130,7 @@
     }
 
     public void setUrl(Image image, String url) {
-      DOM.setElementProperty(image.getElement(), "src", url);
+      DOM.setImgSrc(image.getElement(), url);
     }
 
     public void setUrlAndVisibleRect(Image image, String url, int left,
@@ -278,7 +278,7 @@
    */
   public static void prefetch(String url) {
     Element img = DOM.createImg();
-    DOM.setElementProperty(img, "src", url);
+    DOM.setImgSrc(img, url);
     prefetchImages.put(url, img);
   }
 
diff --git a/user/src/com/google/gwt/user/client/ui/impl/ClippedImageImplIE6.java b/user/src/com/google/gwt/user/client/ui/impl/ClippedImageImplIE6.java
index 46ea137..834f1db 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/ClippedImageImplIE6.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/ClippedImageImplIE6.java
@@ -27,6 +27,17 @@
  */
 public class ClippedImageImplIE6 extends ClippedImageImpl {
   
+  private static native void injectGlobalHandler() /*-{
+    $wnd.__gwt_transparentImgHandler = function (elem) {
+      elem.onerror = null;
+      @com.google.gwt.user.client.DOM::setImgSrc(Lcom/google/gwt/user/client/Element;Ljava/lang/String;)(elem, "clear.cache.gif");
+    };
+  }-*/;
+  
+  public ClippedImageImplIE6() {
+    injectGlobalHandler();
+  }
+
   public void adjust(Element clipper, String url, int left, int top, int width,
       int height) {
 
@@ -59,8 +70,9 @@
         + -left + "px; margin-top: " + -top + "px" + "border: 0px";
 
     String clippedImgHtml = "<gwt:clipper style=\""
-        + clipperStyle + "\"><img src='clear.cache.gif' style=\"" + imgStyle
-        + "\" width=" + (left + width) + " height=" + (top + height)
+        + clipperStyle
+        + "\"><img src='about:blank' onerror='if(window.__gwt_transparentImgHandler)window.__gwt_transparentImgHandler(this);else this.src=\"clear.cache.gif\"' style=\""
+        + imgStyle + "\" width=" + (left + width) + " height=" + (top + height)
         + " border='0'></gwt:clipper>";
 
     return clippedImgHtml;
diff --git a/user/test/com/google/gwt/user/client/ui/DOMTest.java b/user/test/com/google/gwt/user/client/ui/DOMTest.java
index 9613a94..cc8384d 100644
--- a/user/test/com/google/gwt/user/client/ui/DOMTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DOMTest.java
@@ -174,7 +174,7 @@
     // Test <img src="http://.../logo.gif" />
     Element image = DOM.createImg();
     String imageUrl = "http://www.google.com/images/logo.gif";
-    DOM.setElementProperty(image, "src", imageUrl);
+    DOM.setImgSrc(image, imageUrl);
     String imageToString = DOM.toString(image).trim().toLowerCase();
     assertTrue(imageToString.startsWith("<img"));
     assertTrue(imageToString.indexOf(imageUrl) != -1);