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);