Committing tree speed up code based on 
  * avoiding creation of images 
  * avoiding creation of table elements 
  * cloning when possible
  * delaying rendering costs

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2430 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java
index 38fec29..e7ddd75 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -112,14 +112,14 @@
   }
 
   static native boolean shouldTreeDelegateFocusToElement(Element elem) /*-{
-     var name = elem.nodeName;
-     return ((name == "SELECT") ||
-         (name == "INPUT")  ||
-         (name == "TEXTAREA") ||
-         (name == "OPTION") ||
-         (name == "BUTTON") ||
-         (name == "LABEL"));
-   }-*/;
+    var name = elem.nodeName;
+    return ((name == "SELECT") ||
+        (name == "INPUT")  ||
+        (name == "TEXTAREA") ||
+        (name == "OPTION") ||
+        (name == "BUTTON") ||
+        (name == "LABEL"));
+    }-*/;
 
   /**
    * Map of TreeItem.widget -> TreeItem.
@@ -140,15 +140,20 @@
    * a duplicate key down.
    */
   private int lastEventType;
+  private Image leafImage;
+  private Image openImage;
+  private Image closedImage;
+
+  private String indentValue;
 
   /**
    * Constructs an empty tree.
    */
   public Tree() {
     if (LocaleInfo.getCurrentLocale().isRTL()) {
-      init(GWT.<TreeImagesRTL>create(TreeImagesRTL.class));      
+      init(GWT.<TreeImagesRTL> create(TreeImagesRTL.class), false);
     } else {
-      init(GWT.<TreeImages>create(TreeImages.class));
+      init(GWT.<TreeImages> create(TreeImages.class), false);
     }
   }
 
@@ -158,7 +163,19 @@
    * @param images a bundle that provides tree specific images
    */
   public Tree(TreeImages images) {
-    init(images);
+    init(images, true);
+  }
+
+  /**
+   * Constructs a tree that uses the specified image bundle for images. If this
+   * tree does not use leaf images, the width of the TreeImage's leaf image will
+   * control the leaf indent.
+   * 
+   * @param images a bundle that provides tree specific images
+   * @param useLeafImages use leaf images from bundle
+   */
+  public Tree(TreeImages images, boolean useLeafImages) {
+    init(images, useLeafImages);
   }
 
   /**
@@ -425,24 +442,24 @@
               break;
             }
             case KeyboardListener.KEY_LEFT: {
-              
+
               if (LocaleInfo.getCurrentLocale().isRTL()) {
                 maybeExpandTreeItem();
               } else {
                 maybeCollapseTreeItem();
               }
-              
+
               DOM.eventPreventDefault(event);
               break;
             }
             case KeyboardListener.KEY_RIGHT: {
-              
+
               if (LocaleInfo.getCurrentLocale().isRTL()) {
                 maybeCollapseTreeItem();
               } else {
                 maybeExpandTreeItem();
               }
-              
+
               DOM.eventPreventDefault(event);
               break;
             }
@@ -681,9 +698,9 @@
 
   void maybeUpdateSelection(TreeItem itemThatChangedState, boolean isItemOpening) {
     /**
-     * If we just closed the item, let's check to see if this item is the parent of
-     * the currently selected item. If so, we should make this item the currently selected
-     * selected item.
+     * If we just closed the item, let's check to see if this item is the parent
+     * of the currently selected item. If so, we should make this item the
+     * currently selected selected item.
      */
     if (!isItemOpening) {
       TreeItem tempItem = curSelection;
@@ -696,6 +713,7 @@
       }
     }
   }
+
   void orphan(Widget widget) {
     // Validation should already be done.
     assert (widget.getParent() == this);
@@ -708,9 +726,42 @@
   }
 
   /**
+   * Called only from {@link TreeItem}: Shows the closed image on that tree
+   * item.
+   * 
+   * @param treeItem the tree item
+   */
+  void showClosedImage(TreeItem treeItem) {
+    showImage(treeItem, closedImage);
+  }
+
+  /**
+   * Called only from {@link TreeItem}: Shows the leaf image on a tree item.
+   * 
+   * @param treeItem the tree item
+   */
+  void showLeafImage(TreeItem treeItem) {
+    if (leafImage != null) {
+      showImage(treeItem, leafImage);
+    } else {
+      DOM.setStyleAttribute(treeItem.getElement(), "paddingLeft", indentValue);
+    }
+  }
+
+  /**
+   * Called only from {@link TreeItem}: Shows the open image on a tree item.
+   * 
+   * @param treeItem the tree item
+   */
+  void showOpenImage(TreeItem treeItem) {
+    showImage(treeItem, openImage);
+  }
+
+  /**
    * Collects parents going up the element tree, terminated at the tree root.
    */
-  private void collectElementChain(ArrayList<Element> chain, Element hRoot, Element hElem) {
+  private void collectElementChain(ArrayList<Element> chain, Element hRoot,
+      Element hElem) {
     if ((hElem == null) || (hElem == hRoot)) {
       return;
     }
@@ -725,7 +776,8 @@
 
     TreeItem item = findItemByChain(chain, 0, root);
     if (item != null) {
-      if (DOM.isOrHasChild(item.getImageElement(), hElem)) {
+      if (item.getChildCount() > 0
+          && DOM.isOrHasChild(item.getImageElement(), hElem)) {
         item.setState(!item.getState(), true);
         return true;
       } else if (DOM.isOrHasChild(item.getElement(), hElem)) {
@@ -744,7 +796,8 @@
     return findDeepestOpenChild(item.getChild(item.getChildCount() - 1));
   }
 
-  private TreeItem findItemByChain(ArrayList<Element> chain, int idx, TreeItem root) {
+  private TreeItem findItemByChain(ArrayList<Element> chain, int idx,
+      TreeItem root) {
     if (idx == chain.size()) {
       return root;
     }
@@ -765,9 +818,9 @@
   }
 
   /**
-   * Get the top parent above this {@link TreeItem} that is in closed state.  In
+   * Get the top parent above this {@link TreeItem} that is in closed state. In
    * other words, get the parent that is guaranteed to be visible.
-   *
+   * 
    * @param item
    * @return the closed parent, or null if all parents are opened
    */
@@ -783,13 +836,14 @@
     return topClosedParent;
   }
 
-  private void init(TreeImages images) {
-    this.images = images;
+  private void init(TreeImages images, boolean useLeafImages) {
+    setImages(images, useLeafImages);
     setElement(DOM.createDiv());
 
     DOM.setStyleAttribute(getElement(), "position", "relative");
-    
-    // Fix rendering problem with relatively-positioned elements and their children by
+
+    // Fix rendering problem with relatively-positioned elements and their
+    // children by
     // forcing the element that is positioned relatively to 'have layout'
     DOM.setStyleAttribute(getElement(), "zoom", "1");
 
@@ -848,6 +902,7 @@
         DOM.removeChild(Tree.this.getElement(), item.getElement());
       }
     };
+    root.initChildren();
     root.setTree(this);
     setStyleName("gwt-Tree");
 
@@ -855,7 +910,7 @@
     Accessibility.setRole(getElement(), Accessibility.ROLE_TREE);
     Accessibility.setRole(focusable, Accessibility.ROLE_TREEITEM);
   }
-  
+
   private void maybeCollapseTreeItem() {
 
     TreeItem topClosedParent = getTopClosedParent(curSelection);
@@ -924,14 +979,15 @@
       // Scroll it into view.
       DOM.scrollIntoView(focusable);
 
-        // Update ARIA attributes to reflect the information from the newly-selected item.
-        updateAriaAttributes();
-      
-        // Ensure Focus is set, as focus may have been previously delegated by
-        // tree.
-        setFocus(true);
-      }
+      // Update ARIA attributes to reflect the information from the
+      // newly-selected item.
+      updateAriaAttributes();
+
+      // Ensure Focus is set, as focus may have been previously delegated by
+      // tree.
+      setFocus(true);
     }
+  }
 
   /**
    * Moves to the next item, going into children as if dig is enabled.
@@ -947,7 +1003,7 @@
       moveSelectionDown(topClosedParent, false);
       return;
     }
-    
+
     TreeItem parent = sel.getParentItem();
     if (parent == null) {
       parent = root;
@@ -975,7 +1031,7 @@
       onSelection(topClosedParent, true, true);
       return;
     }
-    
+
     TreeItem parent = sel.getParentItem();
     if (parent == null) {
       parent = root;
@@ -1014,11 +1070,37 @@
     }
   }
 
+  private void setImages(TreeImages images, boolean useLeafImages) {
+    if (useLeafImages) {
+      leafImage = images.treeLeaf().createImage();
+    } else {
+      Image image = images.treeLeaf().createImage();
+      DOM.setStyleAttribute(image.getElement(), "visibility", "hidden");
+      RootPanel.get().add(image);
+      int size = image.getWidth() + TreeItem.IMAGE_PAD;
+      image.removeFromParent();
+      indentValue = (size) + "px";
+    }
+
+    openImage = images.treeOpen().createImage();
+    closedImage = images.treeClosed().createImage();
+  }
+
+  private void showImage(TreeItem treeItem, Image image) {
+    Element element = treeItem.getImageHolderElement();
+    Element child = DOM.getFirstChild(element);
+    if (child != null) {
+      DOM.removeChild(element, child);
+    }
+    DOM.appendChild(element, DOM.clone(image.getElement(), true));
+  }
+
   private void updateAriaAttributes() {
 
     Element curSelectionContentElem = curSelection.getContentElem();
 
-    // Set the 'aria-level' state. To do this, we need to compute the level of the
+    // Set the 'aria-level' state. To do this, we need to compute the level of
+    // the
     // currently selected item.
 
     // We initialize itemLevel to -1 because the level value is zero-based.
@@ -1038,7 +1120,8 @@
         String.valueOf(curSelectionLevel + 1));
 
     // Set the 'aria-setsize' and 'aria-posinset' states. To do this, we need to
-    // compute the the number of siblings that the currently selected item has, and
+    // compute the the number of siblings that the currently selected item has,
+    // and
     // the item's position among its siblings.
 
     TreeItem curSelectionParent = curSelection.getParentItem();
@@ -1046,35 +1129,42 @@
       curSelectionParent = root;
     }
 
-    Accessibility.setState(curSelectionContentElem, Accessibility.STATE_SETSIZE,
+    Accessibility.setState(curSelectionContentElem,
+        Accessibility.STATE_SETSIZE,
         String.valueOf(curSelectionParent.getChildCount()));
 
     int curSelectionIndex = curSelectionParent.getChildIndex(curSelection);
 
-    Accessibility.setState(curSelectionContentElem, Accessibility.STATE_POSINSET,
-        String.valueOf(curSelectionIndex + 1));
+    Accessibility.setState(curSelectionContentElem,
+        Accessibility.STATE_POSINSET, String.valueOf(curSelectionIndex + 1));
 
-    // Set the 'aria-expanded' state. This depends on the state of the currently selected item.
+    // Set the 'aria-expanded' state. This depends on the state of the currently
+    // selected item.
     // If the item has no children, we remove the 'aria-expanded' state.
 
     if (curSelection.getChildCount() == 0) {
-      Accessibility.removeState(curSelectionContentElem, Accessibility.STATE_EXPANDED);
+      Accessibility.removeState(curSelectionContentElem,
+          Accessibility.STATE_EXPANDED);
     } else {
       if (curSelection.getState()) {
-        Accessibility.setState(curSelectionContentElem, Accessibility.STATE_EXPANDED, "true");
+        Accessibility.setState(curSelectionContentElem,
+            Accessibility.STATE_EXPANDED, "true");
       } else {
-        Accessibility.setState(curSelectionContentElem, Accessibility.STATE_EXPANDED, "false");
+        Accessibility.setState(curSelectionContentElem,
+            Accessibility.STATE_EXPANDED, "false");
       }
     }
 
     // Make sure that 'aria-selected' is true.
 
-    Accessibility.setState(curSelectionContentElem, Accessibility.STATE_SELECTED, "true");
+    Accessibility.setState(curSelectionContentElem,
+        Accessibility.STATE_SELECTED, "true");
 
-    // Update the 'aria-activedescendant' state for the focusable element to match the id
+    // Update the 'aria-activedescendant' state for the focusable element to
+    // match the id
     // of the currently selected item
 
     Accessibility.setState(focusable, Accessibility.STATE_ACTIVEDESCENDANT,
         DOM.getElementAttribute(curSelectionContentElem, "id"));
   }
-    }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/client/ui/TreeItem.java b/user/src/com/google/gwt/user/client/ui/TreeItem.java
index 4fe5f4f..ff44440 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -26,22 +26,24 @@
 /**
  * An item that can be contained within a
  * {@link com.google.gwt.user.client.ui.Tree}.
- *
+ * 
  * Each tree item is assigned a unique DOM id in order to support ARIA. See
  * {@link com.google.gwt.user.client.ui.Accessibility} for more information.
- *
+ * 
  * <p>
  * <h3>Example</h3>
  * {@example com.google.gwt.examples.TreeExample}
  * </p>
  */
 public class TreeItem extends UIObject implements HasHTML {
+
   /**
    * An {@link WidgetAnimation} used to open the child elements. If a
    * {@link TreeItem} is in the process of opening, it will immediately be
    * opened and the new {@link TreeItem} will use this animation.
    */
   private static class TreeItemAnimation extends WidgetAnimation {
+
     /**
      * The {@link TreeItem} currently being affected.
      */
@@ -92,10 +94,12 @@
     public void onUpdate(double progress) {
       int scrollHeight = DOM.getElementPropertyInt(curItem.childSpanElem,
           "scrollHeight");
+
       int height = (int) (progress * scrollHeight);
       if (!opening) {
         height = scrollHeight - height;
       }
+
       DOM.setStyleAttribute(curItem.childSpanElem, "height", height + "px");
 
       // We need to set the width explicitly of the item might be cropped
@@ -125,62 +129,74 @@
     }
   }
 
+  // By not overwriting the default tree padding and spacing, we traditionally
+  // added 7 pixels between our image and content.
+  // <2>|<1>image<1>|<2>|<1>content
+  // So to preserve the current spacing we must add a 7 pixel pad when no image
+  // is supplied.
+  static final int IMAGE_PAD = 7;
+
   /**
    * The static animation used to open {@link TreeItem}s.
    */
-  private static TreeItemAnimation itemAnimation;
+  private static TreeItemAnimation itemAnimation = new TreeItemAnimation();
 
-  private ArrayList<TreeItem> children = new ArrayList<TreeItem>();
-  private Element itemTable, contentElem, childSpanElem;
-  private final Image statusImage = new Image();
+  /**
+   * The structured table to hold images.
+   */
+
+  private static Element BASE_INTERNAL_ELEM;
+  /**
+   * The base tree item element that will be cloned.
+   */
+  private static Element BASE_BARE_ELEM;
+  /**
+   * Static constructor to set up clonable elements.
+   */
+  static {
+    // Create the base table element that will be cloned.
+    BASE_INTERNAL_ELEM = DOM.createTable();
+    setStyleName(BASE_INTERNAL_ELEM, "gwt-TreeItem");
+    Element contentElem = DOM.createSpan();
+    Element tbody = DOM.createTBody(), tr = DOM.createTR();
+    Element tdImg = DOM.createTD(), tdContent = DOM.createTD();
+    DOM.appendChild(BASE_INTERNAL_ELEM, tbody);
+    DOM.appendChild(tbody, tr);
+    DOM.appendChild(tr, tdImg);
+    DOM.appendChild(tr, tdContent);
+    DOM.setStyleAttribute(tdImg, "verticalAlign", "middle");
+    DOM.setStyleAttribute(tdContent, "verticalAlign", "middle");
+    DOM.appendChild(tdContent, contentElem);
+    DOM.setStyleAttribute(contentElem, "display", "inline");
+    DOM.setStyleAttribute(BASE_INTERNAL_ELEM, "whiteSpace", "nowrap");
+
+    // Create the base element that will be cloned
+    BASE_BARE_ELEM = DOM.createDiv();
+
+    // Simulates padding from table element.
+    DOM.setStyleAttribute(BASE_BARE_ELEM, "padding", "3px");
+    DOM.appendChild(BASE_BARE_ELEM, contentElem);
+    Accessibility.setRole(contentElem, Accessibility.ROLE_TREEITEM);
+  }
+  private ArrayList<TreeItem> children;
+  private Element contentElem, childSpanElem, imageHolder;
   private boolean open;
   private TreeItem parent;
   private boolean selected;
+
   private Object userObject;
+
   private Tree tree;
+
   private Widget widget;
 
   /**
    * Creates an empty tree item.
    */
   public TreeItem() {
-    setElement(DOM.createDiv());
-    itemTable = DOM.createTable();
-    contentElem = DOM.createSpan();
-    childSpanElem = DOM.createDiv();
-
-    // Uses the following Element hierarchy:
-    // <div (handle)>
-    // <table (itemElem)>
-    // <tr>
-    // <td><img (imgElem)/></td>
-    // <td><span (contents)/></td>
-    // </tr>
-    // </table>
-    // <div (childSpanElem)> children </div>
-    // </div>
-
-    Element tbody = DOM.createTBody(), tr = DOM.createTR();
-    Element tdImg = DOM.createTD(), tdContent = DOM.createTD();
-    DOM.appendChild(itemTable, tbody);
-    DOM.appendChild(tbody, tr);
-    DOM.appendChild(tr, tdImg);
-    DOM.appendChild(tr, tdContent);
-    DOM.setStyleAttribute(tdImg, "verticalAlign", "middle");
-    DOM.setStyleAttribute(tdContent, "verticalAlign", "middle");
-
-    DOM.appendChild(getElement(), itemTable);
-    DOM.appendChild(getElement(), childSpanElem);
-    DOM.appendChild(tdImg, statusImage.getElement());
-    DOM.appendChild(tdContent, contentElem);
-
-    DOM.setStyleAttribute(contentElem, "display", "inline");
-    DOM.setStyleAttribute(getElement(), "whiteSpace", "nowrap");
-    DOM.setStyleAttribute(childSpanElem, "whiteSpace", "nowrap");
-    DOM.setStyleAttribute(childSpanElem, "padding", "0px");
-    setStyleName(contentElem, "gwt-TreeItem", true);
-
-    Accessibility.setRole(contentElem, Accessibility.ROLE_TREEITEM);
+    Element elem = DOM.clone(BASE_BARE_ELEM, true);
+    setElement(elem);
+    contentElem = DOM.getFirstChild(elem);
     DOM.setElementAttribute(contentElem, "id", DOM.createUniqueId());
   }
 
@@ -218,7 +234,7 @@
 
   /**
    * Adds another item as a child to this one.
-   *
+   * 
    * @param item the item to be added
    */
   public void addItem(TreeItem item) {
@@ -227,17 +243,21 @@
       item.remove();
     }
 
+    if (children == null) {
+      initChildren();
+    }
+
     // Logical attach.
     item.setParentItem(this);
     children.add(item);
 
     // Physical attach.
     if (LocaleInfo.getCurrentLocale().isRTL()) {
-      DOM.setStyleAttribute(item.getElement(), "marginRight", "16px");      
+      DOM.setStyleAttribute(item.getElement(), "marginRight", "16px");
     } else {
-      DOM.setStyleAttribute(item.getElement(), "marginLeft", "16px");      
+      DOM.setStyleAttribute(item.getElement(), "marginLeft", "16px");
     }
-  
+
     DOM.appendChild(childSpanElem, item.getElement());
 
     // Adopt.
@@ -268,7 +288,7 @@
    */
 
   public TreeItem getChild(int index) {
-    if ((index < 0) || (index >= children.size())) {
+    if ((index < 0) || (index >= getChildCount())) {
       return null;
     }
 
@@ -282,6 +302,9 @@
    */
 
   public int getChildCount() {
+    if (children == null) {
+      return 0;
+    }
     return children.size();
   }
 
@@ -293,6 +316,9 @@
    */
 
   public int getChildIndex(TreeItem child) {
+    if (children == null) {
+      return -1;
+    }
     return children.indexOf(child);
   }
 
@@ -380,7 +406,7 @@
 
   public void removeItem(TreeItem item) {
     // Validate.
-    if (!children.contains(item)) {
+    if (children == null || !children.contains(item)) {
       return;
     }
 
@@ -424,7 +450,7 @@
       return;
     }
     this.selected = selected;
-    setStyleName(contentElem, "gwt-TreeItem-selected", selected);
+    setStyleName(getContentElem(), "gwt-TreeItem-selected", selected);
   }
 
   /**
@@ -444,7 +470,7 @@
    *          fired
    */
   public void setState(boolean open, boolean fireEvents) {
-    if (open && children.size() == 0) {
+    if (open && getChildCount() == 0) {
       return;
     }
 
@@ -532,7 +558,6 @@
   /**
    * <b>Affected Elements:</b>
    * <ul>
-   * <li>-image = The status image.</li>
    * <li>-content = The text or {@link Widget} next to the image.</li>
    * <li>-child# = The child at the specified index.</li>
    * </ul>
@@ -542,18 +567,24 @@
   @Override
   protected void onEnsureDebugId(String baseID) {
     super.onEnsureDebugId(baseID);
-    statusImage.ensureDebugId(baseID + "-image");
     ensureDebugId(contentElem, baseID, "content");
+    if (imageHolder != null) {
+      // The image itself may or may not exist.
+      ensureDebugId(imageHolder, baseID, "image");
+    }
 
-    int childCount = 0;
-    for (TreeItem child : children) {
-      child.ensureDebugId(baseID + "-child" + childCount);
-      childCount++;
+    if (children != null) {
+      int childCount = 0;
+      for (TreeItem child : children) {
+        child.ensureDebugId(baseID + "-child" + childCount);
+        childCount++;
+      }
     }
   }
 
   void addTreeItems(List<TreeItem> accum) {
-    for (int i = 0; i < children.size(); i++) {
+    int size = getChildCount();
+    for (int i = 0; i < size; i++) {
       TreeItem item = children.get(i);
       accum.add(item);
       item.addTreeItems(accum);
@@ -568,12 +599,24 @@
     return contentElem;
   }
 
-  int getContentHeight() {
-    return DOM.getElementPropertyInt(itemTable, "offsetHeight");
+  Element getImageElement() {
+    return DOM.getFirstChild(getImageHolderElement());
   }
 
-  Element getImageElement() {
-    return statusImage.getElement();
+  Element getImageHolderElement() {
+    if (imageHolder == null) {
+      convertToFullNode();
+    }
+    return imageHolder;
+  }
+
+  void initChildren() {
+    convertToFullNode();
+    childSpanElem = DOM.createDiv();
+    DOM.appendChild(getElement(), childSpanElem);
+    DOM.setStyleAttribute(childSpanElem, "whiteSpace", "nowrap");
+    DOM.setStyleAttribute(childSpanElem, "overflow", "hidden");
+    children = new ArrayList<TreeItem>();
   }
 
   void setParentItem(TreeItem parent) {
@@ -598,7 +641,7 @@
     }
 
     tree = newTree;
-    for (int i = 0, n = children.size(); i < n; ++i) {
+    for (int i = 0, n = getChildCount(); i < n; ++i) {
       children.get(i).setTree(newTree);
     }
     updateState(false, true);
@@ -613,23 +656,21 @@
 
   void updateState(boolean animate, boolean updateTreeSelection) {
     // If the tree hasn't been set, there is no visual state to update.
-    if (tree == null) {
+    // If the tree is not attached, then update will be called on attach.
+    if (tree == null || tree.isAttached() == false) {
       return;
     }
 
-    TreeImages images = tree.getImages();
-
-    if (children.size() == 0) {
-      UIObject.setVisible(childSpanElem, false);
-      images.treeLeaf().applyTo(statusImage);
+    if (getChildCount() == 0) {
+      if (childSpanElem != null) {
+        UIObject.setVisible(childSpanElem, false);
+      }
+      tree.showLeafImage(this);
       return;
     }
 
     // We must use 'display' rather than 'visibility' here,
     // or the children will always take up space.
-    if (itemAnimation == null) {
-      itemAnimation = new TreeItemAnimation();
-    }
     if (animate && (tree != null) && (tree.isAttached())) {
       itemAnimation.setItemState(this, tree.isAnimationEnabled());
     } else {
@@ -638,14 +679,15 @@
 
     // Change the status image
     if (open) {
-      images.treeOpen().applyTo(statusImage);
+      tree.showOpenImage(this);
     } else {
-      images.treeClosed().applyTo(statusImage);
+      tree.showClosedImage(this);
     }
 
     // We may need to update the tree's selection in response to a tree state change. For
     // example, if the tree's currently selected item is a descendant of an item whose
     // branch was just collapsed, then the item itself should become the newly-selected item.
+    // itself should become the newly-selected item.
     if (updateTreeSelection) {
       tree.maybeUpdateSelection(this, this.open);
     }
@@ -656,9 +698,26 @@
     tree.maybeUpdateSelection(this, this.open);
   }
 
+  private void convertToFullNode() {
+    if (imageHolder == null) {
+      // Extract the Elements from the object
+      Element itemTable = DOM.clone(BASE_INTERNAL_ELEM, true);
+      DOM.appendChild(getElement(), itemTable);
+      Element tr = DOM.getFirstChild(DOM.getFirstChild(itemTable));
+      Element tdImg = DOM.getFirstChild(tr);
+      Element tdContent = DOM.getNextSibling(tdImg);
+
+      // Undoes padding from table element.
+      DOM.setStyleAttribute(getElement(), "padding", "0px");
+      DOM.setStyleAttribute(getElement(), "paddingLeft", "0px");
+      DOM.appendChild(tdContent, contentElem);
+      imageHolder = tdImg;
+    }
+  }
+
   private void updateStateRecursiveHelper() {
     updateState(false, false);
-    for (int i = 0, n = children.size(); i < n; ++i) {
+    for (int i = 0, n = getChildCount(); i < n; ++i) {
       children.get(i).updateStateRecursiveHelper();
     }
   }
diff --git a/user/test/com/google/gwt/user/client/ui/TreeTest.java b/user/test/com/google/gwt/user/client/ui/TreeTest.java
index 6f08a37..d263d9f 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeTest.java
@@ -88,10 +88,13 @@
         bottom1.getElement());
     UIObjectTest.assertDebugId("myTree-root-child3-child2",
         bottom2.getElement());
-    
+
     // Check tree item sub elements
-    UIObjectTest.assertDebugId("myTree-root-child0-content", top0.getContentElem());
-    UIObjectTest.assertDebugId("myTree-root-child0-image", top0.getImageElement());
+    UIObjectTest.assertDebugId("myTree-root-child0-content",
+        top0.getContentElem());
+
+    UIObjectTest.assertDebugId("myTree-root-child3-image",
+        top3.getImageHolderElement());
   }
 
   public void testInsertSameItemRepeatedly() {