blob: 92ed3188c71a1e40710022bc3508cfa296615fc7 [file] [log] [blame]
/*
* 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.dom.client;
import com.google.gwt.core.client.JavaScriptObject;
/**
* 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.
*/
class ImageSrcIE6 {
/**
* 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.
*/
private static JavaScriptObject srcImgMap;
static {
executeBackgroundImageCacheCommand();
}
/**
* Returns the src of the image, or the pending src if the image is pending.
*/
public static native String getImgSrc(Element img) /*-{
return img.__pendingSrc || img.src;
}-*/;
/**
* Sets the src of the image, queuing up with other requests for the same URL
* if necessary. If the element is a clone, it may think it is in a pending
* state but will not be updated properly when the image loads, so we need to
* add it to the queue.
*/
public static void setImgSrc(Element img, String src) {
// We can't early out yet because the img may be a clone that needs to be
// cleaned up and added to the list of children.
boolean isSameSource = getImgSrc(img).equals(src);
// Lazy init the map
if (srcImgMap == null) {
srcImgMap = JavaScriptObject.createObject();
}
// See if this element is already pending.
String oldSrc = getPendingSrc(img);
if (oldSrc != null) {
// The element is pending; there must be a top node for this src.
Element top = getTop(srcImgMap, oldSrc);
if (top == null) {
// This is a clone, so clean it up.
cleanupExpandos(img);
} else if (top.equals(img)) {
if (isSameSource) {
// Early out as this is the top element, and therefore not a clone.
return;
}
// It's a pending parent.
removeTop(srcImgMap, top);
} else if (removeChild(top, img, isSameSource)) {
// It's a pending child.
if (isSameSource) {
// Early out as this is a known child, and therefore not a clone.
return;
}
} else {
// The child wasn't found, so this is a clone.
cleanupExpandos(img);
}
}
// Now load the new src URL.
Element top = getTop(srcImgMap, src);
if (top == null) {
// There is no existing pending parent.
addTop(srcImgMap, img, src);
} else {
// There is an existing pending parent.
addChild(top, img);
}
}
/**
* Adds an image as a child to a pending parent.
*/
private static native void addChild(Element parent, Element child) /*-{
parent.__kids.push(child);
child.__pendingSrc = parent.__pendingSrc;
}-*/;
/**
* Sets an image as the pending parent for the specified URL.
*/
private static native void addTop(JavaScriptObject srcImgMap, Element img,
String src) /*-{
// 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.
img.__kids = [];
img.__pendingSrc = src;
srcImgMap[src] = img;
var _onload = img.onload, _onerror = img.onerror, _onabort = img.onabort;
// Same cleanup code matter what state we end up in.
function finish(_originalHandler) {
// Grab a copy of the kids.
var kids = img.__kids;
img.__cleanup();
// Set the src for all kids in a timer to ensure caching has happened.
window.setTimeout(function() {
for (var i = 0; i < kids.length; ++i) {
var kid = kids[i];
if (kid.__pendingSrc == src) {
kid.src = src;
kid.__pendingSrc = null;
}
}
}, 0);
// Call the original handler, if any.
_originalHandler && _originalHandler.call(img);
}
img.onload = function() {
finish(_onload);
}
img.onerror = function() {
finish(_onerror);
}
img.onabort = function() {
finish(_onabort);
}
img.__cleanup = function() {
img.onload = _onload;
img.onerror = _onerror;
img.onabort = _onabort;
img.__cleanup = img.__pendingSrc = img.__kids = null;
delete srcImgMap[src];
}
}-*/;
/**
* Cleanup an img element that was created using cloneNode.
*
* @param img the img element
*/
private static native void cleanupExpandos(Element img) /*-{
img.__cleanup = img.__pendingSrc = img.__kids = null;
}-*/;
private static native void executeBackgroundImageCacheCommand() /*-{
// 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
}
}-*/;
/**
* Returns the pending src URL of an image, or <code>null</code> if the image
* has no pending src URL.
*/
private static native String getPendingSrc(Element img) /*-{
return img.__pendingSrc;
}-*/;
/**
* Returns the pending parent for the specified URL, or <code>null</code> if
* there is no pending parent for the specified URL.
*/
private static native Element getTop(JavaScriptObject srcImgMap, String src) /*-{
return srcImgMap[src];
}-*/;
/**
* Removes a child image from its pending parent. If checkOnly is true, the
* method will only check that the child is a child of the parent.
*
* @param parent the top element that is loading the source
* @param child the child to remove
* @param checkOnly if true, only verify that the child is a child of parent
* @return true if the child is found, false if not
*/
private static native boolean removeChild(Element parent, Element child,
boolean checkOnly) /*-{
var kids = parent.__kids;
for (var i = 0, c = kids.length; i < c; ++i) {
if (kids[i] === child) {
if (!checkOnly) {
kids.splice(i, 1);
child.__pendingSrc = null;
}
return true;
}
}
// If the child isn't in any kids lists, it must be a clone.
return false;
}-*/;
/**
* Removes a pending parent's pending status, in preparation for changing its
* URL to something else.
*/
private static native void removeTop(JavaScriptObject srcImgMap, Element img) /*-{
var src = img.__pendingSrc;
var kids = img.__kids;
img.__cleanup();
// Restructure the kids, if any.
if (img = kids[0]) {
// Try to elect a new top node.
img.__pendingSrc = null;
@com.google.gwt.dom.client.ImageSrcIE6::addTop(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/dom/client/Element;Ljava/lang/String;)(srcImgMap, img, src);
if (img.__pendingSrc) {
// It became a top node, add the rest as children.
kids.splice(0, 1);
img.__kids = kids;
} else {
// It loaded immediately; just finish the rest.
// This is an extremely unlikely case, but could theoretically happen
// depending on how a browser's network and UI threads are synchronized.
for (var i = 1, c = kids.length; i < c; ++i) {
kids[i].src = src;
kids[i].__pendingSrc = null;
}
}
}
}-*/;
}