Fixed a bug in PopupPanel where it could enter an invalid state if show() is called while it is in the process of hiding.

Patch by: jlabanca
Review by: ecc (peer programming)



git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4692 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/PopupPanel.java b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
index 542b653..b732c69 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -466,10 +466,7 @@
     if (!isShowing()) {
       return;
     }
-    cleanup();
-
-    // Hide the popup
-    resizeAnimation.setState(false);
+    setState(false, true);
     CloseEvent.fire(this, this, autoClosed);
   }
 
@@ -782,13 +779,7 @@
     if (showing) {
       return;
     }
-    showing = true;
-    nativePreviewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
-      public void onPreviewNativeEvent(NativePreviewEvent event) {
-        previewNativeEvent(event);
-      }
-    });
-    resizeAnimation.setState(true);
+    setState(true, true);
   }
 
   /**
@@ -827,13 +818,15 @@
       event.cancel();
     }
   }
- 
+
   @Override
   protected void onUnload() {
     // Just to be sure, we perform cleanup when the popup is unloaded (i.e.
     // removed from the DOM). This is normally taken care of in hide(), but it
     // can be missed if someone removes the popup directly from the RootPanel.
-    cleanup();
+    if (isShowing()) {
+      setState(false, false);
+    }
   }
 
   /**
@@ -894,16 +887,6 @@
     }
   }-*/;
 
-  private void cleanup() {
-    // Clear the 'showing' flag and make sure that the event preview is cleaned
-    // up.
-    showing = false;
-    if (nativePreviewHandlerRegistration != null) {
-      nativePreviewHandlerRegistration.removeHandler();
-      nativePreviewHandlerRegistration = null;
-    }
-  }
-
   /**
    * Does the event target one of the partner elements?
    * 
@@ -1174,4 +1157,33 @@
       }
     }
   }
+
+  /**
+   * Set the showing state of the popup. If maybeAnimate is true, the animation
+   * will be used to set the state. If it is false, the animation will be
+   * cancelled.
+   * 
+   * @param showing the new state
+   * @param maybeAnimate true to possibly run the animation
+   */
+  private void setState(boolean showing, boolean maybeAnimate) {
+    if (maybeAnimate) {
+      resizeAnimation.setState(showing);
+    } else {
+      resizeAnimation.cancel();
+    }
+    this.showing = showing;
+
+    // Create or remove the native preview handler
+    if (showing) {
+      nativePreviewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
+        public void onPreviewNativeEvent(NativePreviewEvent event) {
+          previewNativeEvent(event);
+        }
+      });
+    } else if (nativePreviewHandlerRegistration != null) {
+      nativePreviewHandlerRegistration.removeHandler();
+      nativePreviewHandlerRegistration = null;
+    }
+  }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/PopupTest.java b/user/test/com/google/gwt/user/client/ui/PopupTest.java
index a8611bd..fd735a9 100644
--- a/user/test/com/google/gwt/user/client/ui/PopupTest.java
+++ b/user/test/com/google/gwt/user/client/ui/PopupTest.java
@@ -180,6 +180,28 @@
     }
   }
 
+  /**
+   * Test the showing a popup while it is hiding will not result in an illegal
+   * state.
+   */
+  public void testShowWhileHiding() {
+    PopupPanel popup = createPopupPanel();
+
+    // Show the popup
+    popup.setAnimationEnabled(false);
+    popup.show();
+    assertTrue(popup.isShowing());
+
+    // Start hiding the popup
+    popup.setAnimationEnabled(true);
+    popup.hide();
+    assertFalse(popup.isShowing());
+
+    // Show the popup while its hiding
+    popup.show();
+    assertTrue(popup.isShowing());
+  }
+
   public void testPopup() {
     // Get rid of window margins so we can test absolute position.
     Window.setMargin("0px");