Fixes a bug where all Buttons default to submit in safari, causing forms to automatically submit when the button is pressed.  This patch introduces a button class for each button type.

Patch by: t.broyer
Review by: jlabanca
Issue: 1585, 3962



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6022 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
index 743694e..7527e09 100644
--- a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
@@ -49,6 +49,7 @@
     addIssue(new Issue2553());
     addIssue(new Issue2855());
     addIssue(new Issue3172());
+    addIssue(new Issue3962());
     addIssue(new Issue3973());
   }
 
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3962.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3962.java
new file mode 100644
index 0000000..b0e6587
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3962.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2009 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.museum.client.defaultmuseum;
+
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
+
+/**
+ * Buttons default to type=submit in WebKit (Safari and Chrome) and IE8 (in IE8
+ * mode).
+ */
+public class Issue3962 extends AbstractIssue {
+  private FormPanel form;
+
+  @Override
+  public Widget createIssue() {
+    form = new FormPanel();
+    form.addSubmitHandler(new SubmitHandler() {
+      public void onSubmit(SubmitEvent event) {
+        Window.alert("Form is being submitted.");
+        event.cancel();
+      }
+    });
+    form.setWidget(new Button("Submit"));
+    return form;
+  }
+
+  @Override
+  public String getInstructions() {
+    return "Click the button, it should have no effect.";
+  }
+
+  @Override
+  public String getSummary() {
+    return "In IE8 (in IE8 mode) and WebKit (Safari and Chrome), buttons default"
+        + " to type submit.";
+  }
+
+  @Override
+  public boolean hasCSS() {
+    return false;
+  }
+}
diff --git a/user/src/com/google/gwt/dom/client/DOMImpl.java b/user/src/com/google/gwt/dom/client/DOMImpl.java
index e5ae74b..b9ba351 100644
--- a/user/src/com/google/gwt/dom/client/DOMImpl.java
+++ b/user/src/com/google/gwt/dom/client/DOMImpl.java
@@ -25,6 +25,12 @@
     button.click();
   }-*/;
 
+  public native ButtonElement createButtonElement(Document doc, String type) /*-{
+    var e = doc.createElement("BUTTON");
+    e.type = type;
+    return e;
+  }-*/;
+
   public native Element createElement(Document doc, String tag) /*-{
     return doc.createElement(tag);
   }-*/;
diff --git a/user/src/com/google/gwt/dom/client/DOMImplTrident.java b/user/src/com/google/gwt/dom/client/DOMImplTrident.java
index 74b4e19..4bc7ae9 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplTrident.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplTrident.java
@@ -21,12 +21,18 @@
    * This field *must* be filled in from JSNI code before dispatching an event
    * on IE. It should be set to the 'this' context of the handler that receives
    * the event, then restored to its initial value when the dispatcher is done.
-   * See {@link com.google.gwt.user.client.impl.DOMImplTrident#initEventSystem()}
+   * See
+   * {@link com.google.gwt.user.client.impl.DOMImplTrident#initEventSystem()}
    * for an example of how this should be done.
    */
   private static EventTarget currentEventTarget;
 
   @Override
+  public native ButtonElement createButtonElement(Document doc, String type) /*-{
+    return doc.createElement("<BUTTON type='" + type + "'></BUTTON>");
+  }-*/;
+
+  @Override
   public Element createElement(Document doc, String tagName) {
     if (tagName.contains(":")) {
       // Special implementation for tag names with namespace-prefixes. The only
@@ -48,8 +54,8 @@
   }
 
   @Override
-  public native NativeEvent createHtmlEvent(Document doc, String type, boolean canBubble,
-      boolean cancelable) /*-{
+  public native NativeEvent createHtmlEvent(Document doc, String type,
+      boolean canBubble, boolean cancelable) /*-{
     // NOTE: IE doesn't support changing bubbling and canceling behavior (this
     // is documented publicly in Document.createHtmlEvent()).
     var evt = doc.createEventObject();
@@ -63,9 +69,9 @@
   }-*/;
 
   @Override
-  public native NativeEvent createKeyEvent(Document doc, String type, boolean canBubble,
-      boolean cancelable, boolean ctrlKey, boolean altKey, boolean shiftKey,
-      boolean metaKey, int keyCode, int charCode) /*-{
+  public native NativeEvent createKeyEvent(Document doc, String type,
+      boolean canBubble, boolean cancelable, boolean ctrlKey, boolean altKey,
+      boolean shiftKey, boolean metaKey, int keyCode, int charCode) /*-{
     // NOTE: IE doesn't support changing bubbling and canceling behavior (this
     // is documented publicly in Document.createKeyEvent()).
     var evt = doc.createEventObject();
@@ -81,10 +87,10 @@
   }-*/;
 
   @Override
-  public native NativeEvent createMouseEvent(Document doc, String type, boolean canBubble,
-      boolean cancelable, int detail, int screenX, int screenY, int clientX,
-      int clientY, boolean ctrlKey, boolean altKey, boolean shiftKey,
-      boolean metaKey, int button, Element relatedTarget) /*-{
+  public native NativeEvent createMouseEvent(Document doc, String type,
+      boolean canBubble, boolean cancelable, int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
+      boolean shiftKey, boolean metaKey, int button, Element relatedTarget) /*-{
     // NOTE: IE doesn't support changing bubbling and canceling behavior (this
     // is documented publicly in Document.createMouseEvent()).
     var evt = doc.createEventObject();
@@ -168,7 +174,7 @@
 
   /**
    * IE returns a numeric type for some attributes that are really properties,
-   * such as offsetWidth.  We need to coerce these to strings to prevent a
+   * such as offsetWidth. We need to coerce these to strings to prevent a
    * runtime JS exception.
    */
   @Override
@@ -192,6 +198,7 @@
     return elem.innerText;
   }-*/;
 
+  @Override
   public String getTagName(Element elem) {
     String tagName = getTagNameInternal(elem);
     String scopeName = getScopeNameInternal(elem);
diff --git a/user/src/com/google/gwt/dom/client/Document.java b/user/src/com/google/gwt/dom/client/Document.java
index b962621..d9ce69d 100644
--- a/user/src/com/google/gwt/dom/client/Document.java
+++ b/user/src/com/google/gwt/dom/client/Document.java
@@ -68,7 +68,8 @@
    * @return the newly created element
    */
   public final QuoteElement createBlockQuoteElement() {
-    return (QuoteElement) DOMImpl.impl.createElement(this, QuoteElement.TAG_BLOCKQUOTE);
+    return (QuoteElement) DOMImpl.impl.createElement(this,
+        QuoteElement.TAG_BLOCKQUOTE);
   }
 
   /**
@@ -89,11 +90,27 @@
 
   /**
    * Creates a &lt;button&gt; element.
+   * <p>
+   * <b>Warning!</b> The button type is actually implementation-dependent and is
+   * read-only.
+   * 
+   * @return the newly created element
+   * @deprecated use {@link #createPushButtonElement()},
+   *             {@link #createResetButtonElement()} or
+   *             {@link #createSubmitButtonElement()} instead.
+   */
+  @Deprecated
+  public final ButtonElement createButtonElement() {
+    return (ButtonElement) DOMImpl.impl.createElement(this, ButtonElement.TAG);
+  }
+
+  /**
+   * Creates an &lt;input type='button'&gt; element.
    * 
    * @return the newly created element
    */
-  public final ButtonElement createButtonElement() {
-    return (ButtonElement) DOMImpl.impl.createElement(this, ButtonElement.TAG);
+  public final InputElement createButtonInputElement() {
+    return DOMImpl.impl.createInputElement(this, "button");
   }
 
   /**
@@ -102,7 +119,8 @@
    * @return the newly created element
    */
   public final TableCaptionElement createCaptionElement() {
-    return (TableCaptionElement) DOMImpl.impl.createElement(this, TableCaptionElement.TAG);
+    return (TableCaptionElement) DOMImpl.impl.createElement(this,
+        TableCaptionElement.TAG);
   }
 
   /**
@@ -140,8 +158,8 @@
    * @param metaKey <code>true</code> if the meta key is depressed
    * @return the event object
    */
-  public final NativeEvent createClickEvent(int detail, int screenX, int screenY,
-      int clientX, int clientY, boolean ctrlKey, boolean altKey,
+  public final NativeEvent createClickEvent(int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
       boolean shiftKey, boolean metaKey) {
     // We disallow setting the button here, because IE doesn't provide the
     // button property for click events.
@@ -156,7 +174,8 @@
    * @return the newly created element
    */
   public final TableColElement createColElement() {
-    return (TableColElement) DOMImpl.impl.createElement(this, TableColElement.TAG_COL);
+    return (TableColElement) DOMImpl.impl.createElement(this,
+        TableColElement.TAG_COL);
   }
 
   /**
@@ -165,7 +184,8 @@
    * @return the newly created element
    */
   public final TableColElement createColGroupElement() {
-    return (TableColElement) DOMImpl.impl.createElement(this, TableColElement.TAG_COLGROUP);
+    return (TableColElement) DOMImpl.impl.createElement(this,
+        TableColElement.TAG_COLGROUP);
   }
 
   /**
@@ -204,8 +224,8 @@
    * @param metaKey <code>true</code> if the meta key is depressed
    * @return the event object
    */
-  public final NativeEvent createDblClickEvent(int detail, int screenX, int screenY,
-      int clientX, int clientY, boolean ctrlKey, boolean altKey,
+  public final NativeEvent createDblClickEvent(int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
       boolean shiftKey, boolean metaKey) {
     // We disallow setting the button here, because IE doesn't provide the
     // button property for click events.
@@ -266,7 +286,8 @@
    * @return the newly created element
    */
   public final FieldSetElement createFieldSetElement() {
-    return (FieldSetElement) DOMImpl.impl.createElement(this, FieldSetElement.TAG);
+    return (FieldSetElement) DOMImpl.impl.createElement(this,
+        FieldSetElement.TAG);
   }
 
   /**
@@ -311,7 +332,8 @@
    * @return the newly created element
    */
   public final FrameSetElement createFrameSetElement() {
-    return (FrameSetElement) DOMImpl.impl.createElement(this, FrameSetElement.TAG);
+    return (FrameSetElement) DOMImpl.impl.createElement(this,
+        FrameSetElement.TAG);
   }
 
   /**
@@ -577,8 +599,8 @@
    *          {@link NativeEvent#BUTTON_LEFT} et al)
    * @return the event object
    */
-  public final NativeEvent createMouseDownEvent(int detail, int screenX, int screenY,
-      int clientX, int clientY, boolean ctrlKey, boolean altKey,
+  public final NativeEvent createMouseDownEvent(int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
       boolean shiftKey, boolean metaKey, int button) {
     return createMouseEvent("mousedown", true, true, detail, screenX, screenY,
         clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, null);
@@ -643,8 +665,8 @@
    *          {@link NativeEvent#BUTTON_LEFT} et al)
    * @return the event object
    */
-  public final NativeEvent createMouseMoveEvent(int detail, int screenX, int screenY,
-      int clientX, int clientY, boolean ctrlKey, boolean altKey,
+  public final NativeEvent createMouseMoveEvent(int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
       boolean shiftKey, boolean metaKey, int button) {
     return createMouseEvent("mousemove", true, true, detail, screenX, screenY,
         clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, null);
@@ -670,8 +692,8 @@
    * @param relatedTarget the event's related target
    * @return the event object
    */
-  public final NativeEvent createMouseOutEvent(int detail, int screenX, int screenY,
-      int clientX, int clientY, boolean ctrlKey, boolean altKey,
+  public final NativeEvent createMouseOutEvent(int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
       boolean shiftKey, boolean metaKey, int button, Element relatedTarget) {
     return createMouseEvent("mouseout", true, true, detail, screenX, screenY,
         clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button,
@@ -698,8 +720,8 @@
    * @param relatedTarget the event's related target
    * @return the event object
    */
-  public final NativeEvent createMouseOverEvent(int detail, int screenX, int screenY,
-      int clientX, int clientY, boolean ctrlKey, boolean altKey,
+  public final NativeEvent createMouseOverEvent(int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
       boolean shiftKey, boolean metaKey, int button, Element relatedTarget) {
     return createMouseEvent("mouseover", true, true, detail, screenX, screenY,
         clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button,
@@ -722,8 +744,8 @@
    *          {@link NativeEvent#BUTTON_LEFT} et al)
    * @return the event object
    */
-  public final NativeEvent createMouseUpEvent(int detail, int screenX, int screenY,
-      int clientX, int clientY, boolean ctrlKey, boolean altKey,
+  public final NativeEvent createMouseUpEvent(int detail, int screenX,
+      int screenY, int clientX, int clientY, boolean ctrlKey, boolean altKey,
       boolean shiftKey, boolean metaKey, int button) {
     return createMouseEvent("mouseup", true, true, detail, screenX, screenY,
         clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, null);
@@ -753,7 +775,8 @@
    * @return the newly created element
    */
   public final OptGroupElement createOptGroupElement() {
-    return (OptGroupElement) DOMImpl.impl.createElement(this, OptGroupElement.TAG);
+    return (OptGroupElement) DOMImpl.impl.createElement(this,
+        OptGroupElement.TAG);
   }
 
   /**
@@ -789,7 +812,8 @@
    * @return the newly created element
    */
   public final ParagraphElement createPElement() {
-    return (ParagraphElement) DOMImpl.impl.createElement(this, ParagraphElement.TAG);
+    return (ParagraphElement) DOMImpl.impl.createElement(this,
+        ParagraphElement.TAG);
   }
 
   /**
@@ -802,6 +826,15 @@
   }
 
   /**
+   * Creates a &lt;button type='button'&gt; element.
+   * 
+   * @return the newly created element
+   */
+  public final ButtonElement createPushButtonElement() {
+    return DOMImpl.impl.createButtonElement(this, "button");
+  }
+
+  /**
    * Creates a &lt;q&gt; element.
    * 
    * @return the newly created element
@@ -821,6 +854,24 @@
   }
 
   /**
+   * Creates a &lt;button type='reset'&gt; element.
+   * 
+   * @return the newly created element
+   */
+  public final ButtonElement createResetButtonElement() {
+    return DOMImpl.impl.createButtonElement(this, "reset");
+  }
+
+  /**
+   * Creates an &lt;input type='reset'&gt; element.
+   * 
+   * @return the newly created element
+   */
+  public final InputElement createResetInputElement() {
+    return DOMImpl.impl.createInputElement(this, "reset");
+  }
+
+  /**
    * Creates a &lt;script&gt; element.
    * 
    * @return the newly created element
@@ -889,6 +940,15 @@
   }
 
   /**
+   * Creates a &lt;button type='submit'&gt; element.
+   * 
+   * @return the newly created element
+   */
+  public final ButtonElement createSubmitButtonElement() {
+    return DOMImpl.impl.createButtonElement(this, "submit");
+  }
+
+  /**
    * Creates an &lt;input type='submit'&gt; element.
    * 
    * @return the newly created element
@@ -912,7 +972,8 @@
    * @return the newly created element
    */
   public final TableSectionElement createTBodyElement() {
-    return (TableSectionElement) DOMImpl.impl.createElement(this, TableSectionElement.TAG_TBODY);
+    return (TableSectionElement) DOMImpl.impl.createElement(this,
+        TableSectionElement.TAG_TBODY);
   }
 
   /**
@@ -921,7 +982,8 @@
    * @return the newly created element
    */
   public final TableCellElement createTDElement() {
-    return (TableCellElement) DOMImpl.impl.createElement(this, TableCellElement.TAG_TD);
+    return (TableCellElement) DOMImpl.impl.createElement(this,
+        TableCellElement.TAG_TD);
   }
 
   /**
@@ -930,7 +992,8 @@
    * @return the newly created element
    */
   public final TextAreaElement createTextAreaElement() {
-    return (TextAreaElement) DOMImpl.impl.createElement(this, TextAreaElement.TAG);
+    return (TextAreaElement) DOMImpl.impl.createElement(this,
+        TextAreaElement.TAG);
   }
 
   /**
@@ -958,7 +1021,8 @@
    * @return the newly created element
    */
   public final TableSectionElement createTFootElement() {
-    return (TableSectionElement) DOMImpl.impl.createElement(this, TableSectionElement.TAG_TFOOT);
+    return (TableSectionElement) DOMImpl.impl.createElement(this,
+        TableSectionElement.TAG_TFOOT);
   }
 
   /**
@@ -967,7 +1031,8 @@
    * @return the newly created element
    */
   public final TableSectionElement createTHeadElement() {
-    return (TableSectionElement) DOMImpl.impl.createElement(this, TableSectionElement.TAG_THEAD);
+    return (TableSectionElement) DOMImpl.impl.createElement(this,
+        TableSectionElement.TAG_THEAD);
   }
 
   /**
@@ -976,7 +1041,8 @@
    * @return the newly created element
    */
   public final TableCellElement createTHElement() {
-    return (TableCellElement) DOMImpl.impl.createElement(this, TableCellElement.TAG_TH);
+    return (TableCellElement) DOMImpl.impl.createElement(this,
+        TableCellElement.TAG_TH);
   }
 
   /**
@@ -994,7 +1060,8 @@
    * @return the newly created element
    */
   public final TableRowElement createTRElement() {
-    return (TableRowElement) DOMImpl.impl.createElement(this, TableRowElement.TAG);
+    return (TableRowElement) DOMImpl.impl.createElement(this,
+        TableRowElement.TAG);
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/client/ui/Button.java b/user/src/com/google/gwt/user/client/ui/Button.java
index 0f7863c..895483b 100644
--- a/user/src/com/google/gwt/user/client/ui/Button.java
+++ b/user/src/com/google/gwt/user/client/ui/Button.java
@@ -28,9 +28,10 @@
  * </p>
  * 
  * <h3>CSS Style Rules</h3>
- * <ul class="css">
- * <li>.gwt-Button { }</li>
- * </ul>
+ * <dl>
+ * <dt>.gwt-Button</dt>
+ * <dd>the outer element</dd>
+ * </dl>
  * 
  * <p>
  * <h3>Example</h3>
@@ -53,6 +54,7 @@
     assert Document.get().getBody().isOrHasChild(element);
 
     Button button = new Button(element);
+    assert "button".equalsIgnoreCase(button.getButtonElement().getType());
 
     // Mark it attached and remember it for cleanup.
     button.onAttach();
@@ -61,22 +63,11 @@
     return button;
   }
 
-  static native void adjustType(Element button) /*-{
-    // Check before setting this attribute, as not all browsers define it.
-    if (button.type == 'submit') {
-      try { 
-        button.setAttribute("type", "button"); 
-      } catch (e) { 
-      }
-    }
-  }-*/;
-
   /**
    * Creates a button with no caption.
    */
   public Button() {
-    super(Document.get().createButtonElement());
-    adjustType(getElement());
+    super(Document.get().createPushButtonElement());
     setStyleName("gwt-Button");
   }
 
@@ -132,8 +123,12 @@
     getButtonElement().click();
   }
 
-  private ButtonElement getButtonElement() {
+  /**
+   * Get the underlying button element.
+   * 
+   * @return the {@link ButtonElement}
+   */
+  protected ButtonElement getButtonElement() {
     return getElement().cast();
   }
 }
-
diff --git a/user/src/com/google/gwt/user/client/ui/ResetButton.java b/user/src/com/google/gwt/user/client/ui/ResetButton.java
new file mode 100644
index 0000000..83bc66c
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/ResetButton.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2009 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.user.client.ui;
+
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.ClickHandler;
+
+/**
+ * A standard push-button widget which will automatically reset its enclosing
+ * {@link FormPanel} if any.
+ * 
+ * <h3>CSS Style Rules</h3>
+ * <dl>
+ * <dt>.gwt-ResetButton</dt>
+ * <dd>the outer element</dd>
+ * </dl>
+ */
+public class ResetButton extends Button {
+
+  /**
+   * Creates a ResetButton widget that wraps an existing &lt;button&gt; element.
+   * 
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
+   * 
+   * @param element the element to be wrapped
+   */
+  public static Button wrap(com.google.gwt.dom.client.Element element) {
+    // Assert that the element is attached.
+    assert Document.get().getBody().isOrHasChild(element);
+
+    ResetButton button = new ResetButton(element);
+
+    // Mark it attached and remember it for cleanup.
+    button.onAttach();
+    RootPanel.detachOnWindowClose(button);
+
+    return button;
+  }
+
+  /**
+   * Creates a button with no caption.
+   */
+  public ResetButton() {
+    super(Document.get().createResetButtonElement());
+    setStyleName("gwt-ResetButton");
+  }
+
+  /**
+   * Creates a button with the given HTML caption.
+   * 
+   * @param html the HTML caption
+   */
+  public ResetButton(String html) {
+    this();
+    setHTML(html);
+  }
+
+  /**
+   * Creates a button with the given HTML caption and click listener.
+   * 
+   * @param html the HTML caption
+   * @param handler the click handler
+   */
+  public ResetButton(String html, ClickHandler handler) {
+    this(html);
+    addClickHandler(handler);
+  }
+
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be a &lt;button&gt; element with type reset.
+   * 
+   * @param element the element to be used
+   */
+  protected ResetButton(com.google.gwt.dom.client.Element element) {
+    super(element);
+    assert "reset".equalsIgnoreCase(element.<ButtonElement> cast().getType());
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/SubmitButton.java b/user/src/com/google/gwt/user/client/ui/SubmitButton.java
new file mode 100644
index 0000000..c86f466
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/SubmitButton.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2009 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.user.client.ui;
+
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.ClickHandler;
+
+/**
+ * A standard push-button widget which will automatically submit its enclosing
+ * {@link FormPanel} if any.
+ * 
+ * <h3>CSS Style Rules</h3>
+ * <dl>
+ * <dt>.gwt-SubmitButton</dt>
+ * <dd>the outer element</dd>
+ * </dl>
+ */
+public class SubmitButton extends Button {
+
+  /**
+   * Creates a SubmitButton widget that wraps an existing &lt;button&gt;
+   * element.
+   * 
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
+   * 
+   * @param element the element to be wrapped
+   */
+  public static Button wrap(com.google.gwt.dom.client.Element element) {
+    // Assert that the element is attached.
+    assert Document.get().getBody().isOrHasChild(element);
+
+    SubmitButton button = new SubmitButton(element);
+    assert "submit".equalsIgnoreCase(button.getButtonElement().getType());
+
+    // Mark it attached and remember it for cleanup.
+    button.onAttach();
+    RootPanel.detachOnWindowClose(button);
+
+    return button;
+  }
+
+  /**
+   * Creates a button with no caption.
+   */
+  public SubmitButton() {
+    super(Document.get().createSubmitButtonElement());
+    setStyleName("gwt-SubmitButton");
+  }
+
+  /**
+   * Creates a button with the given HTML caption.
+   * 
+   * @param html the HTML caption
+   */
+  public SubmitButton(String html) {
+    this();
+    setHTML(html);
+  }
+
+  /**
+   * Creates a button with the given HTML caption and click listener.
+   * 
+   * @param html the HTML caption
+   * @param handler the click handler
+   */
+  public SubmitButton(String html, ClickHandler handler) {
+    this(html);
+    addClickHandler(handler);
+  }
+
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be a &lt;button&gt; element with type submit.
+   * 
+   * @param element the element to be used
+   */
+  protected SubmitButton(com.google.gwt.dom.client.Element element) {
+    super(element);
+    assert "submit".equalsIgnoreCase(element.<ButtonElement> cast().getType());
+  }
+}
diff --git a/user/test/com/google/gwt/dom/client/DocumentTest.java b/user/test/com/google/gwt/dom/client/DocumentTest.java
index 2cf11b8..dc66082 100644
--- a/user/test/com/google/gwt/dom/client/DocumentTest.java
+++ b/user/test/com/google/gwt/dom/client/DocumentTest.java
@@ -37,7 +37,6 @@
     assertEquals("blockquote",
         doc.createBlockQuoteElement().getTagName().toLowerCase());
     assertEquals("br", doc.createBRElement().getTagName().toLowerCase());
-    assertEquals("button", doc.createButtonElement().getTagName().toLowerCase());
     assertEquals("caption",
         doc.createCaptionElement().getTagName().toLowerCase());
     assertEquals("col", doc.createColElement().getTagName().toLowerCase());
@@ -91,6 +90,20 @@
     assertEquals("tr", doc.createTRElement().getTagName().toLowerCase());
     assertEquals("ul", doc.createULElement().getTagName().toLowerCase());
 
+    assertEquals("button",
+        doc.createPushButtonElement().getTagName().toLowerCase());
+    assertEquals("button",
+        doc.createResetButtonElement().getTagName().toLowerCase());
+    assertEquals("button",
+        doc.createSubmitButtonElement().getTagName().toLowerCase());
+
+    assertEquals("button",
+        doc.createPushButtonElement().getType().toLowerCase());
+    assertEquals("reset",
+        doc.createResetButtonElement().getType().toLowerCase());
+    assertEquals("submit",
+        doc.createSubmitButtonElement().getType().toLowerCase());
+
     assertEquals("input",
         doc.createCheckInputElement().getTagName().toLowerCase());
     assertEquals("input",
@@ -106,6 +119,8 @@
     assertEquals("input",
         doc.createTextInputElement().getTagName().toLowerCase());
 
+    assertEquals("button",
+        doc.createButtonInputElement().getType().toLowerCase());
     assertEquals("checkbox",
         doc.createCheckInputElement().getType().toLowerCase());
     assertEquals("file", doc.createFileInputElement().getType().toLowerCase());
@@ -116,6 +131,9 @@
         doc.createPasswordInputElement().getType().toLowerCase());
     assertEquals("radio",
         doc.createRadioInputElement("foo").getType().toLowerCase());
+    assertEquals("reset", doc.createResetInputElement().getType().toLowerCase());
+    assertEquals("submit",
+        doc.createSubmitInputElement().getType().toLowerCase());
     assertEquals("text", doc.createTextInputElement().getType().toLowerCase());
   }
 
diff --git a/user/test/com/google/gwt/dom/client/ElementTest.java b/user/test/com/google/gwt/dom/client/ElementTest.java
index 6d19163..b332018 100644
--- a/user/test/com/google/gwt/dom/client/ElementTest.java
+++ b/user/test/com/google/gwt/dom/client/ElementTest.java
@@ -75,7 +75,7 @@
    * Test round-trip of the 'disabled' property.
    */
   public void testDisabled() {
-    ButtonElement button = Document.get().createButtonElement();
+    ButtonElement button = Document.get().createPushButtonElement();
     assertFalse(button.isDisabled());
     button.setDisabled(true);
     assertTrue(button.isDisabled());
diff --git a/user/test/com/google/gwt/user/client/ui/ButtonTest.java b/user/test/com/google/gwt/user/client/ui/ButtonTest.java
index 0fcf4dd..f570f20 100644
--- a/user/test/com/google/gwt/user/client/ui/ButtonTest.java
+++ b/user/test/com/google/gwt/user/client/ui/ButtonTest.java
@@ -19,12 +19,16 @@
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
 
 /**
  * Tests for {@link Button}.
  */
 public class ButtonTest extends GWTTestCase {
 
+  @Override
   public String getModuleName() {
     return "com.google.gwt.user.User";
   }
@@ -39,6 +43,26 @@
     }
   }
 
+  private static class H2 implements SubmitHandler {
+    boolean submitted;
+
+    public void onSubmit(SubmitEvent event) {
+      submitted = true;
+      event.cancel();
+    }
+  }
+
+  public void testButton() {
+    Button pushButton = new Button();
+    assertEquals("button", pushButton.getButtonElement().getType());
+
+    ResetButton resetButton = new ResetButton();
+    assertEquals("reset", resetButton.getButtonElement().getType());
+
+    SubmitButton submitButton = new SubmitButton();
+    assertEquals("submit", submitButton.getButtonElement().getType());
+  }
+
   public void testClick() {
     Button b = new Button();
     RootPanel.get().add(b);
@@ -53,5 +77,30 @@
     // synthesized clicks. This tests the workaround in DOMImplMozillaOld.
     assertEquals(b.getElement(), h.target);
   }
-}
 
+  /**
+   * Tests issues 1585 and 3962: a button shouldn't submit a form.
+   */
+  public void testPushButton() {
+    FormPanel f = new FormPanel();
+    f.setAction("javascript:''");
+    RootPanel.get().add(f);
+
+    Button b = new Button();
+    f.setWidget(b);
+
+    final H2 h = new H2();
+    f.addSubmitHandler(h);
+
+    delayTestFinish(5000);
+    new Timer() {
+      @Override
+      public void run() {
+        assertFalse(h.submitted);
+        finishTest();
+      }
+    }.schedule(2500);
+
+    b.click();
+  }
+}