Revision: 7646
Author: [email protected]
Date: Wed Mar  3 05:43:42 2010
Log: Fixing possible NPE in ImageSrcIE6 when an img element is copied.
http://gwt-code-reviews.appspot.com/150807

http://code.google.com/p/google-web-toolkit/source/detail?r=7646

Added:
 /trunk/user/test/com/google/gwt/dom/client/ImageElementTest.java
 /trunk/user/test/com/google/gwt/dom/public-test
 /trunk/user/test/com/google/gwt/dom/public-test/largeImage0.jpg
 /trunk/user/test/com/google/gwt/dom/public-test/largeImage1.jpg
 /trunk/user/test/com/google/gwt/dom/public-test/largeImage2.jpg
 /trunk/user/test/com/google/gwt/dom/public-test/smallImage0.jpg
 /trunk/user/test/com/google/gwt/dom/public-test/smallImage1.jpg
 /trunk/user/test/com/google/gwt/dom/public-test/smallImage2.jpg
Modified:
 /trunk/user/src/com/google/gwt/dom/client/DOMImplIE6.java
 /trunk/user/src/com/google/gwt/dom/client/ImageElement.java
 /trunk/user/src/com/google/gwt/dom/client/ImageSrcIE6.java
 /trunk/user/src/com/google/gwt/layout/client/LayoutImplIE6.java
 /trunk/user/src/com/google/gwt/user/client/ui/impl/ClippedImageImplIE6.java
 /trunk/user/test/com/google/gwt/dom/DOMTest.gwt.xml

=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dom/client/ImageElementTest.java Wed Mar 3 05:43:42 2010
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2008 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.junit.client.GWTTestCase;
+
+/**
+ * Tests for {...@link ImageElement}.
+ */
+public class ImageElementTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dom.DOMTest";
+  }
+
+  /**
+ * IE6 has a special implementation of {...@link ImageElement#setSrc(String)} + * that prevents the browser from loading uncached images multiple times. In + * order to test this properly, the browser should be set to clear its cache
+   * on exit.
+   */
+  public void testSetSrc() {
+    // The parent element will actually load the image.
+    ImageElement parent = Document.get().createImageElement();
+    parent.setSrc("largeImage0.jpg");
+    assertTrue(parent.getSrc().endsWith("largeImage0.jpg"));
+
+ // The child element does not have its source set until the parent loads.
+    ImageElement child = Document.get().createImageElement();
+    child.setSrc("largeImage0.jpg");
+    assertTrue(child.getSrc().endsWith("largeImage0.jpg"));
+
+    child.setSrc("smallImage1.jpg");
+    assertTrue(child.getSrc().endsWith("smallImage1.jpg"));
+  }
+
+  public void testSetSrcCloneParent() {
+    // The parent element will actually load the image.
+    ImageElement parent = Document.get().createImageElement();
+    parent.setSrc("largeImage1.jpg");
+    assertTrue(parent.getSrc().endsWith("largeImage1.jpg"));
+
+ // The child element does not have its source set until the parent loads.
+    ImageElement child = Document.get().createImageElement();
+    child.setSrc("largeImage1.jpg");
+    assertTrue(child.getSrc().endsWith("largeImage1.jpg"));
+
+ // The parent clone will have its source set. We call setSrc to convert it
+    // to a child.
+    final ImageElement cloneParent = parent.cloneNode(true).cast();
+    cloneParent.setSrc("largeImage1.jpg");
+    assertTrue(cloneParent.getSrc().endsWith("largeImage1.jpg"));
+
+    cloneParent.setSrc("smallImage1.jpg");
+    assertTrue(cloneParent.getSrc().endsWith("smallImage1.jpg"));
+  }
+
+  public void testSetSrcCloneChild() {
+    // The parent element will actually load the image.
+    ImageElement parent = Document.get().createImageElement();
+    parent.setSrc("largeImage2.jpg");
+    assertTrue(parent.getSrc().endsWith("largeImage2.jpg"));
+
+ // The child element does not have its source set until the parent loads.
+    ImageElement child = Document.get().createImageElement();
+    child.setSrc("largeImage2.jpg");
+    assertTrue(child.getSrc().endsWith("largeImage2.jpg"));
+
+ // The child clone will not have its source set. We call setSrc to ensure
+    // it is registered as a child.
+    final ImageElement cloneChild = parent.cloneNode(true).cast();
+    cloneChild.setSrc("largeImage2.jpg");
+    assertTrue(cloneChild.getSrc().endsWith("largeImage2.jpg"));
+
+    cloneChild.setSrc("smallImage2.jpg");
+    assertTrue(cloneChild.getSrc().endsWith("smallImage2.jpg"));
+  }
+
+}
=======================================
--- /dev/null   
+++ /trunk/user/test/com/google/gwt/dom/public-test/largeImage0.jpg Wed Mar 3 05:43:42 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /trunk/user/test/com/google/gwt/dom/public-test/largeImage1.jpg Wed Mar 3 05:43:42 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /trunk/user/test/com/google/gwt/dom/public-test/largeImage2.jpg Wed Mar 3 05:43:42 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /trunk/user/test/com/google/gwt/dom/public-test/smallImage0.jpg Wed Mar 3 05:43:42 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /trunk/user/test/com/google/gwt/dom/public-test/smallImage1.jpg Wed Mar 3 05:43:42 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /trunk/user/test/com/google/gwt/dom/public-test/smallImage2.jpg Wed Mar 3 05:43:42 2010
Binary file, no diff available.
=======================================
--- /trunk/user/src/com/google/gwt/dom/client/DOMImplIE6.java Wed Jan 27 05:45:28 2010 +++ /trunk/user/src/com/google/gwt/dom/client/DOMImplIE6.java Wed Mar 3 05:43:42 2010
@@ -21,6 +21,47 @@
  */
 class DOMImplIE6 extends DOMImplTrident {

+  private static boolean isIE6;
+  private static boolean isIE6Detected;
+
+  /**
+   * Check if the browser is IE6 or IE7.
+   *
+ * @return <code>true</code> if the browser is IE6, <code>false</code> if IE7
+   *         or any other browser
+   * @deprecated this method may be removed at any time
+   */
+  @Deprecated
+  static boolean isIE6() {
+    if (!isIE6Detected) {
+      isIE6 = isIE6Impl();
+      isIE6Detected = true;
+    }
+    return isIE6;
+  }
+
+  // Stolen and modified from UsernmAgent.gwt.xml.
+ // TODO(jgw): Get rid of this method, and switch to using soft permutations
+  // once they land in trunk.
+  private static native boolean isIE6Impl() /*-{
+    function makeVersion(result) {
+      return (parseInt(result[1]) * 1000) + parseInt(result[2]);
+    }
+
+    var ua = navigator.userAgent.toLowerCase();
+    if (ua.indexOf("msie") != -1) {
+      var result = /msie ([0-9]+)\.([0-9]+)/.exec(ua);
+      if (result && result.length == 3) {
+        var v = makeVersion(result);
+        if (v < 7000) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }-*/;
+
   @Override
   public native void cssClearOpacity(Style style) /*-{
     style.filter = '';
@@ -59,7 +100,11 @@
    */
   @Override
   public String imgGetSrc(Element img) {
-    return ImageSrcIE6.getImgSrc(img);
+    if (isIE6()) {
+      return ImageSrcIE6.getImgSrc(img);
+    } else {
+      return super.imgGetSrc(img);
+    }
   }

   /**
@@ -71,7 +116,11 @@
    */
   @Override
   public void imgSetSrc(Element img, String src) {
-    ImageSrcIE6.setImgSrc(img, src);
+    if (isIE6()) {
+      ImageSrcIE6.setImgSrc(img, src);
+    } else {
+      super.imgSetSrc(img, src);
+    }
   }

   @Override
=======================================
--- /trunk/user/src/com/google/gwt/dom/client/ImageElement.java Fri May 15 13:11:37 2009 +++ /trunk/user/src/com/google/gwt/dom/client/ImageElement.java Wed Mar 3 05:43:42 2010
@@ -112,7 +112,16 @@
    }-*/;

   /**
+   * <p>
    * URI designating the source of this image.
+   * </p>
+   * <p>
+ * If you {...@link #cloneNode(boolean)} an {...@link ImageElement}, or an element
+   * that contains an {...@link ImageElement}, then you must call
+   * {...@link #setSrc(String)} on the cloned element to ensure it is loaded
+ * properly in IE6. Failure to do so may cause performance problems, or your
+   * image may not load due to an IE6 specific workaround.
+   * </p>
    *
* @see <a href="http://www.w3.org/TR/1999/REC-html401-19991224/struct/objects.html#adef-src-IMG";>W3C HTML Specification</a>
    */
=======================================
--- /trunk/user/src/com/google/gwt/dom/client/ImageSrcIE6.java Thu Apr 17 15:10:46 2008 +++ /trunk/user/src/com/google/gwt/dom/client/ImageSrcIE6.java Wed Mar 3 05:43:42 2010
@@ -33,27 +33,29 @@
    * 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. + * 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.
+ * 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) {
-    // Early out for no-op prop set.
-    if (getImgSrc(img).equals(src)) {
-      return;
-    }
+ // 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();
@@ -63,13 +65,26 @@
     if (oldSrc != null) {
       // The element is pending; there must be a top node for this src.
       Element top = getTop(srcImgMap, oldSrc);
-      assert (top != null);
-      if (top.equals(img)) {
+      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 {
+      } else if (removeChild(top, img, isSameSource)) {
         // It's a pending child.
-        removeChild(top, img);
+        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);
       }
     }

@@ -95,10 +110,11 @@
   /**
    * Sets an image as the pending parent for the specified URL.
    */
- private static native void addTop(JavaScriptObject srcImgMap, Element img, String src) /*-{ + 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
@@ -109,9 +125,9 @@
     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.
@@ -142,7 +158,7 @@
     img.onabort = function() {
       finish(_onabort);
     }
-
+
     img.__cleanup = function() {
       img.onload = _onload;
       img.onerror = _onerror;
@@ -152,6 +168,15 @@
     }
   }-*/;

+  /**
+   * 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
@@ -180,17 +205,29 @@
   }-*/;

   /**
-   * Removes a child image from its pending parent.
+ * 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 void removeChild(Element parent, Element child) /*-{
+  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) {
-        kids.splice(i, 1);
-        child.__pendingSrc = null;
-        return;
+        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;
   }-*/;

   /**
=======================================
--- /trunk/user/src/com/google/gwt/layout/client/LayoutImplIE6.java Mon Jan 25 08:52:17 2010 +++ /trunk/user/src/com/google/gwt/layout/client/LayoutImplIE6.java Wed Mar 3 05:43:42 2010
@@ -38,30 +38,6 @@
  */
 class LayoutImplIE6 extends LayoutImplIE8 {

-  private static boolean isIE6 = isIE6();
-
-  // Stolen and modified from UserAgent.gwt.xml.
- // TODO(jgw): Get rid of this method, and switch to using soft permutations
-  // once they land in trunk.
-  private static native boolean isIE6() /*-{
-     function makeVersion(result) {
-       return (parseInt(result[1]) * 1000) + parseInt(result[2]);
-     }
-
-     var ua = navigator.userAgent.toLowerCase();
-     if (ua.indexOf("msie") != -1) {
-       var result = /msie ([0-9]+)\.([0-9]+)/.exec(ua);
-       if (result && result.length == 3) {
-         var v = makeVersion(result);
-         if (v < 7000) {
-           return true;
-         }
-       }
-     }
-
-     return false;
-   }-*/;
-
   private static Element createStyleRuler(Element parent) {
     DivElement styleRuler = Document.get().createDivElement();
     DivElement styleInner = Document.get().createDivElement();
@@ -83,6 +59,10 @@
     });
   }

+  private static native boolean isIE6() /*-{
+    return @com.google.gwt.dom.client.DOMImplIE6::isIE6()();
+  }-*/;
+
   @SuppressWarnings("unused") // called from JSNI
   private static native void measureDecoration(Element elem) /*-{
     var ruler = elem.__styleRuler;
@@ -139,7 +119,7 @@

   @Override
public Element attachChild(Element parent, Element child, Element before) {
-    if (!isIE6) {
+    if (!isIE6()) {
       return super.attachChild(parent, child, before);
     }

@@ -165,7 +145,7 @@

   @Override
   public void fillParent(Element elem) {
-    if (!isIE6) {
+    if (!isIE6()) {
       super.fillParent(elem);
       return;
     }
@@ -175,7 +155,7 @@

   @Override
   public void finalizeLayout(Element parent) {
-    if (!isIE6) {
+    if (!isIE6()) {
       super.finalizeLayout(parent);
       return;
     }
@@ -188,13 +168,14 @@
   public void initParent(Element parent) {
     super.initParent(parent);

-    if (isIE6) {
+    if (isIE6()) {
       setPropertyElement(parent, "__styleRuler", createStyleRuler(parent));
     }
   }

+  @Override
   public void layout(Layer layer) {
-    if (!isIE6) {
+    if (!isIE6()) {
       super.layout(layer);
       return;
     }
@@ -205,7 +186,7 @@

   @Override
   public void onAttach(Element parent) {
-    if (isIE6) {
+    if (isIE6()) {
       // No need to re-connect layer refs. This will be taken care of
       // automatically in layout().
       initResizeHandler(parent);
@@ -215,7 +196,7 @@

   @Override
   public void onDetach(Element parent) {
-    if (isIE6) {
+    if (isIE6()) {
       removeLayerRefs(parent);
       removeResizeHandler(parent);
       removeUnitChangeHandler(relativeRuler);
=======================================
--- /trunk/user/src/com/google/gwt/user/client/ui/impl/ClippedImageImplIE6.java Fri Nov 20 16:10:50 2009 +++ /trunk/user/src/com/google/gwt/user/client/ui/impl/ClippedImageImplIE6.java Wed Mar 3 05:43:42 2010
@@ -31,8 +31,6 @@

   private static String moduleBaseUrlProtocol =
GWT.getHostPageBaseURL().startsWith("https") ? "https://"; : "http://";;
-
-  private static boolean isIE6 = isIE6();

   private static native void injectGlobalHandler() /*-{
     $wnd.__gwt_transparentImgHandler = function (elem) {
@@ -40,28 +38,10 @@
@com.google.gwt.user.client.DOM::setImgSrc(Lcom/google/gwt/user/client/Element;Ljava/lang/String;)(elem, @com.google.gwt.core.client.GWT::getModuleBaseURL()() + "clear.cache.gif");
     };
   }-*/;
-
-  // Stolen and modified from UserAgent.gwt.xml.
- // TODO(jgw): Get rid of this method, and switch to using soft permutations
-  // once they land in trunk.
+
   private static native boolean isIE6() /*-{
-     function makeVersion(result) {
-       return (parseInt(result[1]) * 1000) + parseInt(result[2]);
-     }
-
-     var ua = navigator.userAgent.toLowerCase();
-     if (ua.indexOf("msie") != -1) {
-       var result = /msie ([0-9]+)\.([0-9]+)/.exec(ua);
-       if (result && result.length == 3) {
-         var v = makeVersion(result);
-         if (v < 7000) {
-           return true;
-         }
-       }
-     }
-
-     return false;
-   }-*/;
+    return @com.google.gwt.dom.client.DOMImplIE6::isIE6()();
+  }-*/;

   public ClippedImageImplIE6() {
     injectGlobalHandler();
@@ -70,7 +50,7 @@
   @Override
public void adjust(Element clipper, String url, int left, int top, int width,
       int height) {
-    if (!isIE6) {
+    if (!isIE6()) {
       super.adjust(clipper, url, left, top, width, height);
       return;
     }
@@ -97,7 +77,7 @@
   @Override
   public Element createStructure(String url, int left, int top, int width,
       int height) {
-    if (!isIE6) {
+    if (!isIE6()) {
       return super.createStructure(url, left, top, width, height);
     }

@@ -111,7 +91,7 @@

   @Override
public String getHTML(String url, int left, int top, int width, int height) {
-    if (!isIE6) {
+    if (!isIE6()) {
       return super.getHTML(url, left, top, width, height);
     }

@@ -146,7 +126,7 @@

   @Override
   public Element getImgElement(Image image) {
-    if (!isIE6) {
+    if (!isIE6()) {
       return super.getImgElement(image);
     }
     return image.getElement().getFirstChildElement();
=======================================
--- /trunk/user/test/com/google/gwt/dom/DOMTest.gwt.xml Thu Apr 3 09:07:28 2008 +++ /trunk/user/test/com/google/gwt/dom/DOMTest.gwt.xml Wed Mar 3 05:43:42 2010
@@ -13,4 +13,5 @@
<!-- limitations under the License. -->
 <module>
   <inherits name="com.google.gwt.dom.DOM"/>
+  <public path="public-test"/>
 </module>

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to