Fixing ActionCell and ButtonCell clicks outside of the Button element are ignored.

Issue: 5641
Author: hekke, jlabanca

Review at http://gwt-code-reviews.appspot.com/1371810

Review by: pdr@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9828 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/cell/client/ActionCell.java b/user/src/com/google/gwt/cell/client/ActionCell.java
index c72c63d..fec102c 100644
--- a/user/src/com/google/gwt/cell/client/ActionCell.java
+++ b/user/src/com/google/gwt/cell/client/ActionCell.java
@@ -16,6 +16,7 @@
 package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -76,7 +77,14 @@
       NativeEvent event, ValueUpdater<C> valueUpdater) {
     super.onBrowserEvent(context, parent, value, event, valueUpdater);
     if ("click".equals(event.getType())) {
-      onEnterKeyDown(context, parent, value, event, valueUpdater);
+      EventTarget eventTarget = event.getEventTarget();
+      if (!Element.is(eventTarget)) {
+        return;
+      }
+      if (parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))) {
+        // Ignore clicks that occur outside of the main element.
+        onEnterKeyDown(context, parent, value, event, valueUpdater);
+      }
     }
   }
 
diff --git a/user/src/com/google/gwt/cell/client/ButtonCell.java b/user/src/com/google/gwt/cell/client/ButtonCell.java
index cabd39e..03a56c4 100644
--- a/user/src/com/google/gwt/cell/client/ButtonCell.java
+++ b/user/src/com/google/gwt/cell/client/ButtonCell.java
@@ -16,6 +16,7 @@
 package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -48,7 +49,14 @@
       NativeEvent event, ValueUpdater<String> valueUpdater) {
     super.onBrowserEvent(context, parent, value, event, valueUpdater);
     if ("click".equals(event.getType())) {
-      onEnterKeyDown(context, parent, value, event, valueUpdater);
+      EventTarget eventTarget = event.getEventTarget();
+      if (!Element.is(eventTarget)) {
+        return;
+      }
+      if (parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))) {
+        // Ignore clicks that occur outside of the main element.
+        onEnterKeyDown(context, parent, value, event, valueUpdater);
+      }
     }
   }
 
diff --git a/user/test/com/google/gwt/cell/client/ActionCellTest.java b/user/test/com/google/gwt/cell/client/ActionCellTest.java
index 474375e..2ad8372 100644
--- a/user/test/com/google/gwt/cell/client/ActionCellTest.java
+++ b/user/test/com/google/gwt/cell/client/ActionCellTest.java
@@ -16,9 +16,7 @@
 package com.google.gwt.cell.client;
 
 import com.google.gwt.cell.client.ActionCell.Delegate;
-import com.google.gwt.cell.client.Cell.Context;
 import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 
 /**
@@ -47,14 +45,22 @@
   public void testOnBrowserEvent() {
     MockDelegate<String> delegate = new MockDelegate<String>();
     ActionCell<String> cell = new ActionCell<String>("hello", delegate);
-    Element parent = Document.get().createDivElement();
-    NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
-        false, false, false);
-    Context context = new Context(0, 0, DEFAULT_KEY);
-    cell.onBrowserEvent(context, parent, "test", event, null);
+    NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false);
+    testOnBrowserEvent(cell, getExpectedInnerHtml(), event, "test", null, true);
     delegate.assertLastObject("test");
   }
 
+  /**
+   * Test that events outside of the button element are ignored.
+   */
+  public void testOnBrowserEventOutsideButton() {
+    MockDelegate<String> delegate = new MockDelegate<String>();
+    ActionCell<String> cell = new ActionCell<String>("hello", delegate);
+    NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false);
+    testOnBrowserEvent(cell, getExpectedInnerHtml(), event, "test", null, false);
+    delegate.assertLastObject(null);
+  }
+
   @Override
   protected Cell<String> createCell() {
     Delegate<String> delegate = new MockDelegate<String>();
diff --git a/user/test/com/google/gwt/cell/client/ButtonCellTest.java b/user/test/com/google/gwt/cell/client/ButtonCellTest.java
index 103d98f..a4fbeef 100644
--- a/user/test/com/google/gwt/cell/client/ButtonCellTest.java
+++ b/user/test/com/google/gwt/cell/client/ButtonCellTest.java
@@ -24,11 +24,18 @@
 public class ButtonCellTest extends CellTestBase<String> {
 
   public void testOnBrowserEvent() {
-    NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
-        false, false, false);
+    NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false);
     testOnBrowserEvent(getExpectedInnerHtml(), event, "clickme", "clickme");
   }
 
+  /**
+   * Test that events outside of the button element are ignored.
+   */
+  public void testOnBrowserEventOutsideButton() {
+    NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false);
+    testOnBrowserEvent(createCell(), getExpectedInnerHtml(), event, "clickme", null, false);
+  }
+
   @Override
   protected Cell<String> createCell() {
     return new ButtonCell();
diff --git a/user/test/com/google/gwt/cell/client/CellTestBase.java b/user/test/com/google/gwt/cell/client/CellTestBase.java
index 733b1e5..dcc851e 100644
--- a/user/test/com/google/gwt/cell/client/CellTestBase.java
+++ b/user/test/com/google/gwt/cell/client/CellTestBase.java
@@ -229,7 +229,7 @@
    * Test
    * {@link Cell#onBrowserEvent(Element, Object, Object, NativeEvent, ValueUpdater)}
    * with the specified conditions.
-   *
+   * 
    * @param startHtml the innerHTML of the cell before the test starts
    * @param event the event to fire
    * @param value the cell value
@@ -237,16 +237,39 @@
    *          null if none expected
    * @return the parent element
    */
-  protected Element testOnBrowserEvent(String startHtml, NativeEvent event,
-      final T value, T expectedValue) {
+  protected Element testOnBrowserEvent(String startHtml, NativeEvent event, final T value,
+      T expectedValue) {
+    return testOnBrowserEvent(createCell(), startHtml, event, value, expectedValue, true);
+  }
+
+  /**
+   * Test
+   * {@link Cell#onBrowserEvent(Element, Object, Object, NativeEvent, ValueUpdater)}
+   * with the specified conditions.
+   * 
+   * @param cell the cell to use
+   * @param startHtml the innerHTML of the cell before the test starts
+   * @param event the event to fire
+   * @param value the cell value
+   * @param expectedValue the expected value passed to the value updater, or
+   *          null if none expected
+   * @param dispatchToFirstChild true to dispatch to the first child of the
+   *          rendered parent element, if one is available
+   * @return the parent element
+   */
+  protected Element testOnBrowserEvent(final Cell<T> cell, String startHtml, NativeEvent event,
+      final T value, T expectedValue, boolean dispatchToFirstChild) {
     // Setup the parent element.
     final com.google.gwt.user.client.Element parent = Document.get().createDivElement().cast();
     parent.setInnerHTML(startHtml);
     Document.get().getBody().appendChild(parent);
 
     // If the element has a child, use it as the event target.
-    Element child = parent.getFirstChildElement();
-    Element target = (child == null) ? parent : child;
+    Element target = parent;
+    if (dispatchToFirstChild) {
+      Element child = parent.getFirstChildElement();
+      target = (child == null) ? parent : child;
+    }
 
     // Pass the event to the cell.
     final MockValueUpdater valueUpdater = new MockValueUpdater();
@@ -255,13 +278,11 @@
         try {
           DOM.setEventListener(parent, null);
           Context context = new Context(0, 0, DEFAULT_KEY);
-          createCell().onBrowserEvent(context, parent, value, event,
-              valueUpdater);
+          cell.onBrowserEvent(context, parent, value, event, valueUpdater);
           parent.removeFromParent();
         } catch (Exception e) {
           // We are in an event loop, so events may not propagate out to JUnit.
-          fail("An exception occured while handling the event: "
-              + e.getMessage());
+          fail("An exception occured while handling the event: " + e.getMessage());
         }
       }
     });