Fixes HTMLPanel to keep it from yanking it out of the DOM whenever a widget is added to it.

Issue: 1741
Patch by: jgw, knorton
Review by: knorton


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2134 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/HTMLPanel.java b/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
index fb7e18e..3e6a458 100644
--- a/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
@@ -80,10 +80,10 @@
   }
 
   /**
-   * Performs a {@link DOM#getElementById(String)} after attaching the widget's
+   * Performs a {@link DOM#getElementById(String)} after attaching the panel's
    * element into a hidden DIV in the document's body. Attachment is necessary
-   * to be able to use the native getElementById. The hidden DIV will remain
-   * attached to the DOM until the Widget itself is fully attached.
+   * to be able to use the native getElementById. The panel's element will be
+   * re-attached to its original parent (if any) after the method returns.
    * 
    * @param id the id whose associated element is to be retrieved
    * @return the associated element, or <code>null</code> if none is found
@@ -93,11 +93,25 @@
     if (hiddenDiv == null) {
       hiddenDiv = DOM.createDiv();
       UIObject.setVisible(hiddenDiv, false);
-      DOM.appendChild(hiddenDiv, getElement());
       DOM.appendChild(RootPanel.getBodyElement(), hiddenDiv);
     }
 
+    // Hang on to the panel's original parent and sibling elements so that it
+    // can be replaced.
+    Element origParent = DOM.getParent(getElement());
+    Element origSibling = DOM.getNextSibling(getElement());
+
+    // Attach the panel's element to the hidden div.
+    DOM.appendChild(hiddenDiv, getElement());
+
     // Now that we're attached to the DOM, we can use getElementById.
-    return DOM.getElementById(id);
+    Element child = DOM.getElementById(id);
+
+    // Put the panel's element back where it was.
+    if (origParent != null) {
+      DOM.insertBefore(origParent, getElement(), origSibling);
+    }
+
+    return child;
   }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java b/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
index 74205e4..72494ec 100644
--- a/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
@@ -48,8 +48,54 @@
         DOM.getParent(labelB.getElement()), "id"));
   }
 
+  /**
+   * This is meant to catch an issue created by a faulty optimization. To
+   * optimize add() when the HTMLPanel is unattached, we would originally
+   * move its element to a hidden div so that getElementById() would work.
+   * Unfortunately, we didn't move it back to its original parent, causing
+   * a problem in the case described in this test.
+   */
+  public void testAddPartiallyAttached() {
+    SimplePanel sp = new SimplePanel();
+    HTMLPanel p = new HTMLPanel("<div id='foo'></div>");
+
+    // Add the HTMLPanel to another panel before adding the button.
+    sp.add(p);
+
+    // Add a button the HTMLPanel, causing the panel's element to be attached
+    // to the DOM.
+    p.add(new Button("foo"), "foo");
+
+    // If all goes well, the HTMLPanel's element should still be properly
+    // connected to the SimplePanel's element.
+    assertTrue(DOM.isOrHasChild(sp.getElement(), p.getElement()));
+  }
+
   public void testAttachDetachOrder() {
     HTMLPanel p = new HTMLPanel("<div id='w00t'></div>");
     HasWidgetsTester.testAll(p, new Adder());
   }
+
+  /**
+   * Ensures that attachToDomAndGetElement() puts the HTMLPanel back exactly
+   * where it was in the DOM originally.
+   */
+  public void testAttachDoesntMangleChildOrder() {
+    FlowPanel fp = new FlowPanel();
+
+    Button ba = new Button("before");
+    Button bb = new Button("after");
+
+    HTMLPanel hp = new HTMLPanel("<div id='foo'></div>");
+
+    fp.add(ba);
+    fp.add(hp);
+    fp.add(bb);
+
+    hp.add(new Button("foo"), "foo");
+
+    assertTrue(DOM.isOrHasChild(fp.getElement(), hp.getElement()));
+    assertTrue(DOM.getNextSibling(ba.getElement()) == hp.getElement());
+    assertTrue(DOM.getNextSibling(hp.getElement()) == bb.getElement());
+  }
 }