/* * 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; } } } }-*/; }