001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.client.impl;
017:
018: import com.google.gwt.core.client.JavaScriptObject;
019: import com.google.gwt.user.client.Element;
020:
021: /**
022: * Works around an IE problem where multiple images trying to load at the same
023: * time will generate a request per image. We fix this by only allowing the
024: * first image of a given URL to set its source immediately, but simultaneous
025: * requests for the same URL don't actually get their source set until the
026: * original load is complete.
027: */
028: class ImageSrcIE6 {
029:
030: /**
031: * A native map of image source URL strings to Image objects. All Image
032: * objects with values in this map are waiting on an asynchronous load to
033: * complete and have event handlers hooked. The moment the image finishes
034: * loading, it will be removed from this map.
035: */
036: private static JavaScriptObject srcImgMap;
037:
038: static {
039: executeBackgroundImageCacheCommand();
040: }
041:
042: /**
043: * Returns the src of the image, or the pending src if the image is pending.
044: */
045: public static native String getImgSrc(Element img) /*-{
046: return img.__pendingSrc || img.src;
047: }-*/;
048:
049: /**
050: * Sets the src of the image, queuing up with other requests for the same
051: * URL if necessary.
052: */
053: public static void setImgSrc(Element img, String src) {
054: // Early out for no-op prop set.
055: if (getImgSrc(img).equals(src)) {
056: return;
057: }
058: // Lazy init the map
059: if (srcImgMap == null) {
060: srcImgMap = JavaScriptObject.createObject();
061: }
062: // See if this element is already pending.
063: String oldSrc = getPendingSrc(img);
064: if (oldSrc != null) {
065: // The element is pending; there must be a top node for this src.
066: Element top = getTop(srcImgMap, oldSrc);
067: assert (top != null);
068: if (top.equals(img)) {
069: // It's a pending parent.
070: removeTop(srcImgMap, top);
071: } else {
072: // It's a pending child.
073: removeChild(top, img);
074: }
075: }
076:
077: // Now load the new src URL.
078: Element top = getTop(srcImgMap, src);
079: if (top == null) {
080: // There is no existing pending parent.
081: addTop(srcImgMap, img, src);
082: } else {
083: // There is an existing pending parent.
084: addChild(top, img);
085: }
086: }
087:
088: /**
089: * Adds an image as a child to a pending parent.
090: */
091: private static native void addChild(Element parent, Element child) /*-{
092: parent.__kids.push(child);
093: child.__pendingSrc = parent.__pendingSrc;
094: }-*/;
095:
096: /**
097: * Sets an image as the pending parent for the specified URL.
098: */
099: private static native void addTop(JavaScriptObject srcImgMap,
100: Element img, String src) /*-{
101: // No outstanding requests; load the image.
102: img.src = src;
103:
104: // If the image was in cache, the load may have just happened synchronously.
105: if (img.complete) {
106: // We're done
107: return;
108: }
109:
110: // Image is loading asynchronously; put in map for chaining.
111: img.__kids = [];
112: img.__pendingSrc = src;
113: srcImgMap[src] = img;
114:
115: var _onload = img.onload, _onerror = img.onerror, _onabort = img.onabort;
116:
117: // Same cleanup code matter what state we end up in.
118: function finish(_originalHandler) {
119: // Grab a copy of the kids.
120: var kids = img.__kids;
121: img.__cleanup();
122:
123: // Set the src for all kids in a timer to ensure caching has happened.
124: window.setTimeout(function() {
125: for (var i = 0; i < kids.length; ++i) {
126: var kid = kids[i];
127: if (kid.__pendingSrc == src) {
128: kid.src = src;
129: kid.__pendingSrc = null;
130: }
131: }
132: }, 0);
133:
134: // Call the original handler, if any.
135: _originalHandler && _originalHandler.call(img);
136: }
137:
138: img.onload = function() {
139: finish(_onload);
140: }
141: img.onerror = function() {
142: finish(_onerror);
143: }
144: img.onabort = function() {
145: finish(_onabort);
146: }
147:
148: img.__cleanup = function() {
149: img.onload = _onload;
150: img.onerror = _onerror;
151: img.onabort = _onabort;
152: img.__cleanup = img.__pendingSrc = img.__kids = null;
153: delete srcImgMap[src];
154: }
155: }-*/;
156:
157: private static native void executeBackgroundImageCacheCommand() /*-{
158: // Fix IE background image refresh bug, present through IE6
159: // see http://www.mister-pixel.com/#Content__state=is_that_simple
160: // this only works with IE6 SP1+
161: try {
162: $doc.execCommand("BackgroundImageCache", false, true);
163: } catch (e) {
164: // ignore error on other browsers
165: }
166: }-*/;
167:
168: /**
169: * Returns the pending src URL of an image, or <code>null</code> if the image
170: * has no pending src URL.
171: */
172: private static native String getPendingSrc(Element img) /*-{
173: return img.__pendingSrc || null;
174: }-*/;
175:
176: /**
177: * Returns the pending parent for the specified URL, or <code>null</code> if
178: * there is no pending parent for the specified URL.
179: */
180: private static native Element getTop(JavaScriptObject srcImgMap,
181: String src) /*-{
182: return srcImgMap[src] || null;
183: }-*/;
184:
185: /**
186: * Removes a child image from its pending parent.
187: */
188: private static native void removeChild(Element parent, Element child) /*-{
189: var uniqueID = child.uniqueID;
190: var kids = parent.__kids;
191: for (var i = 0, c = kids.length; i < c; ++i) {
192: if (kids[i].uniqueID == uniqueID) {
193: kids.splice(i, 1);
194: child.__pendingSrc = null;
195: return;
196: }
197: }
198: }-*/;
199:
200: /**
201: * Removes a pending parent's pending status, in preparation for changing its
202: * URL to something else.
203: */
204: private static native void removeTop(JavaScriptObject srcImgMap,
205: Element img) /*-{
206: var src = img.__pendingSrc;
207: var kids = img.__kids;
208: img.__cleanup();
209:
210: // Restructure the kids, if any.
211: if (img = kids[0]) {
212: // Try to elect a new top node.
213: img.__pendingSrc = null;
214: @com.google.gwt.user.client.impl.ImageSrcIE6::addTop(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/user/client/Element;Ljava/lang/String;)(srcImgMap, img, src);
215: if (img.__pendingSrc) {
216: // It became a top node, add the rest as children.
217: kids.splice(0, 1);
218: img.__kids = kids;
219: } else {
220: // It loaded immediately; just finish the rest.
221: // This is an extremely unlikely case, but could theoretically happen
222: // depending on how a browser's network and UI threads are synchronized.
223: for (var i = 1, c = kids.length; i < c; ++i) {
224: kids[i].src = src;
225: kids[i].__pendingSrc = null;
226: }
227: }
228: }
229: }-*/;
230: }
|