1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/HTMLButtonElement.h"
8 #include "mozilla/dom/HTMLButtonElementBinding.h"
9 #include "nsIDOMHTMLFormElement.h"
10 #include "nsAttrValueInlines.h"
11 #include "nsGkAtoms.h"
12 #include "nsIPresShell.h"
13 #include "nsStyleConsts.h"
14 #include "nsPresContext.h"
15 #include "nsIFormControl.h"
17 #include "nsFormSubmission.h"
18 #include "nsFormSubmissionConstants.h"
21 #include "nsIFormControlFrame.h"
22 #include "nsIDOMEvent.h"
23 #include "nsIDocument.h"
24 #include "mozilla/ContentEvents.h"
25 #include "mozilla/EventDispatcher.h"
26 #include "mozilla/EventStateManager.h"
27 #include "mozilla/EventStates.h"
28 #include "mozilla/MouseEvents.h"
29 #include "mozilla/TextEvents.h"
30 #include "nsUnicharUtils.h"
31 #include "nsLayoutUtils.h"
32 #include "nsPresState.h"
34 #include "nsFocusManager.h"
35 #include "mozilla/dom/HTMLFormElement.h"
36 #include "mozAutoDocUpdate.h"
38 #define NS_IN_SUBMIT_CLICK (1 << 0)
39 #define NS_OUTER_ACTIVATE_EVENT (1 << 1)
41 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Button
)
46 static const nsAttrValue::EnumTable kButtonTypeTable
[] = {
47 { "button", NS_FORM_BUTTON_BUTTON
},
48 { "reset", NS_FORM_BUTTON_RESET
},
49 { "submit", NS_FORM_BUTTON_SUBMIT
},
53 // Default type is 'submit'.
54 static const nsAttrValue::EnumTable
* kButtonDefaultType
= &kButtonTypeTable
[2];
57 // Construction, destruction
58 HTMLButtonElement::HTMLButtonElement(already_AddRefed
<mozilla::dom::NodeInfo
>& aNodeInfo
,
59 FromParser aFromParser
)
60 : nsGenericHTMLFormElementWithState(aNodeInfo
),
61 mType(kButtonDefaultType
->value
),
62 mDisabledChanged(false),
63 mInInternalActivate(false),
64 mInhibitStateRestoration(!!(aFromParser
& FROM_PARSER_FRAGMENT
))
66 // Set up our default state: enabled
67 AddStatesSilently(NS_EVENT_STATE_ENABLED
);
70 HTMLButtonElement::~HTMLButtonElement()
76 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLButtonElement
,
77 nsGenericHTMLFormElementWithState
,
80 NS_IMPL_ADDREF_INHERITED(HTMLButtonElement
, Element
)
81 NS_IMPL_RELEASE_INHERITED(HTMLButtonElement
, Element
)
84 // QueryInterface implementation for HTMLButtonElement
85 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLButtonElement
)
86 NS_INTERFACE_TABLE_INHERITED(HTMLButtonElement
,
87 nsIDOMHTMLButtonElement
,
88 nsIConstraintValidation
)
89 NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState
)
91 // nsIConstraintValidation
92 NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLButtonElement
)
95 HTMLButtonElement::SetCustomValidity(const nsAString
& aError
)
97 nsIConstraintValidation::SetCustomValidity(aError
);
105 HTMLButtonElement::UpdateBarredFromConstraintValidation()
107 SetBarredFromConstraintValidation(mType
== NS_FORM_BUTTON_BUTTON
||
108 mType
== NS_FORM_BUTTON_RESET
||
113 HTMLButtonElement::FieldSetDisabledChanged(bool aNotify
)
115 UpdateBarredFromConstraintValidation();
117 nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify
);
120 // nsIDOMHTMLButtonElement
122 NS_IMPL_ELEMENT_CLONE(HTMLButtonElement
)
125 // nsIDOMHTMLButtonElement
128 HTMLButtonElement::GetForm(nsIDOMHTMLFormElement
** aForm
)
130 return nsGenericHTMLFormElementWithState::GetForm(aForm
);
133 NS_IMPL_BOOL_ATTR(HTMLButtonElement
, Autofocus
, autofocus
)
134 NS_IMPL_BOOL_ATTR(HTMLButtonElement
, Disabled
, disabled
)
135 NS_IMPL_ACTION_ATTR(HTMLButtonElement
, FormAction
, formaction
)
136 NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLButtonElement
, FormEnctype
, formenctype
,
137 "", kFormDefaultEnctype
->tag
)
138 NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLButtonElement
, FormMethod
, formmethod
,
139 "", kFormDefaultMethod
->tag
)
140 NS_IMPL_BOOL_ATTR(HTMLButtonElement
, FormNoValidate
, formnovalidate
)
141 NS_IMPL_STRING_ATTR(HTMLButtonElement
, FormTarget
, formtarget
)
142 NS_IMPL_STRING_ATTR(HTMLButtonElement
, Name
, name
)
143 NS_IMPL_STRING_ATTR(HTMLButtonElement
, Value
, value
)
144 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLButtonElement
, Type
, type
,
145 kButtonDefaultType
->tag
)
148 HTMLButtonElement::TabIndexDefault()
154 HTMLButtonElement::IsHTMLFocusable(bool aWithMouse
, bool *aIsFocusable
, int32_t *aTabIndex
)
156 if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse
, aIsFocusable
, aTabIndex
)) {
162 (!aWithMouse
|| nsFocusManager::sMouseFocusesFormControl
) &&
170 HTMLButtonElement::ParseAttribute(int32_t aNamespaceID
,
172 const nsAString
& aValue
,
173 nsAttrValue
& aResult
)
175 if (aNamespaceID
== kNameSpaceID_None
) {
176 if (aAttribute
== nsGkAtoms::type
) {
177 // XXX ARG!! This is major evilness. ParseAttribute
178 // shouldn't set members. Override SetAttr instead
179 bool success
= aResult
.ParseEnumValue(aValue
, kButtonTypeTable
, false);
181 mType
= aResult
.GetEnumValue();
183 mType
= kButtonDefaultType
->value
;
189 if (aAttribute
== nsGkAtoms::formmethod
) {
190 return aResult
.ParseEnumValue(aValue
, kFormMethodTable
, false);
192 if (aAttribute
== nsGkAtoms::formenctype
) {
193 return aResult
.ParseEnumValue(aValue
, kFormEnctypeTable
, false);
197 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
202 HTMLButtonElement::IsDisabledForEvents(uint32_t aMessage
)
204 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(false);
205 nsIFrame
* formFrame
= do_QueryFrame(formControlFrame
);
206 return IsElementDisabledForEvents(aMessage
, formFrame
);
210 HTMLButtonElement::PreHandleEvent(EventChainPreVisitor
& aVisitor
)
212 aVisitor
.mCanHandle
= false;
213 if (IsDisabledForEvents(aVisitor
.mEvent
->message
)) {
217 // Track whether we're in the outermost Dispatch invocation that will
218 // cause activation of the input. That is, if we're a click event, or a
219 // DOMActivate that was dispatched directly, this will be set, but if we're
220 // a DOMActivate dispatched from click handling, it will not be set.
221 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
222 bool outerActivateEvent
=
223 ((mouseEvent
&& mouseEvent
->IsLeftClickEvent()) ||
224 (aVisitor
.mEvent
->message
== NS_UI_ACTIVATE
&&
225 !mInInternalActivate
));
227 if (outerActivateEvent
) {
228 aVisitor
.mItemFlags
|= NS_OUTER_ACTIVATE_EVENT
;
229 if (mType
== NS_FORM_BUTTON_SUBMIT
&& mForm
) {
230 aVisitor
.mItemFlags
|= NS_IN_SUBMIT_CLICK
;
231 // tell the form that we are about to enter a click handler.
232 // that means that if there are scripted submissions, the
233 // latest one will be deferred until after the exit point of the handler.
234 mForm
->OnSubmitClickBegin(this);
238 return nsGenericHTMLElement::PreHandleEvent(aVisitor
);
242 HTMLButtonElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
)
245 if (!aVisitor
.mPresContext
) {
249 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
250 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
251 if (mouseEvent
&& mouseEvent
->IsLeftClickEvent()) {
252 // XXX Activating actually occurs even if it's caused by untrusted event.
253 // Therefore, shouldn't this be always trusted event?
254 InternalUIEvent
actEvent(aVisitor
.mEvent
->mFlags
.mIsTrusted
,
258 nsCOMPtr
<nsIPresShell
> shell
= aVisitor
.mPresContext
->GetPresShell();
260 nsEventStatus status
= nsEventStatus_eIgnore
;
261 mInInternalActivate
= true;
262 shell
->HandleDOMEventWithTarget(this, &actEvent
, &status
);
263 mInInternalActivate
= false;
265 // If activate is cancelled, we must do the same as when click is
266 // cancelled (revert the checkbox to its original value).
267 if (status
== nsEventStatus_eConsumeNoDefault
) {
268 aVisitor
.mEventStatus
= status
;
274 // mForm is null if the event handler removed us from the document (bug 194582).
275 if ((aVisitor
.mItemFlags
& NS_IN_SUBMIT_CLICK
) && mForm
) {
276 // tell the form that we are about to exit a click handler
277 // so the form knows not to defer subsequent submissions
278 // the pending ones that were created during the handler
279 // will be flushed or forgoten.
280 mForm
->OnSubmitClickEnd();
283 if (nsEventStatus_eIgnore
== aVisitor
.mEventStatus
) {
284 switch (aVisitor
.mEvent
->message
) {
288 // For backwards compat, trigger buttons with space or enter
290 WidgetKeyboardEvent
* keyEvent
= aVisitor
.mEvent
->AsKeyboardEvent();
291 if ((keyEvent
->keyCode
== NS_VK_RETURN
&&
292 NS_KEY_PRESS
== aVisitor
.mEvent
->message
) ||
293 (keyEvent
->keyCode
== NS_VK_SPACE
&&
294 NS_KEY_UP
== aVisitor
.mEvent
->message
)) {
295 nsEventStatus status
= nsEventStatus_eIgnore
;
297 WidgetMouseEvent
event(aVisitor
.mEvent
->mFlags
.mIsTrusted
,
298 NS_MOUSE_CLICK
, nullptr,
299 WidgetMouseEvent::eReal
);
300 event
.inputSource
= nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD
;
301 EventDispatcher::Dispatch(static_cast<nsIContent
*>(this),
302 aVisitor
.mPresContext
, &event
, nullptr,
304 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
307 break;// NS_KEY_PRESS
309 case NS_MOUSE_BUTTON_DOWN
:
311 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
312 if (mouseEvent
->button
== WidgetMouseEvent::eLeftButton
) {
313 if (mouseEvent
->mFlags
.mIsTrusted
) {
314 EventStateManager
* esm
=
315 aVisitor
.mPresContext
->EventStateManager();
316 EventStateManager::SetActiveManager(
317 static_cast<EventStateManager
*>(esm
), this);
319 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
321 fm
->SetFocus(this, nsIFocusManager::FLAG_BYMOUSE
|
322 nsIFocusManager::FLAG_NOSCROLL
);
323 mouseEvent
->mFlags
.mMultipleActionsPrevented
= true;
324 } else if (mouseEvent
->button
== WidgetMouseEvent::eMiddleButton
||
325 mouseEvent
->button
== WidgetMouseEvent::eRightButton
) {
326 // cancel all of these events for buttons
327 //XXXsmaug What to do with these events? Why these should be cancelled?
328 if (aVisitor
.mDOMEvent
) {
329 aVisitor
.mDOMEvent
->StopPropagation();
335 // cancel all of these events for buttons
336 //XXXsmaug What to do with these events? Why these should be cancelled?
337 case NS_MOUSE_BUTTON_UP
:
338 case NS_MOUSE_DOUBLECLICK
:
340 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
341 if (aVisitor
.mDOMEvent
&&
342 (mouseEvent
->button
== WidgetMouseEvent::eMiddleButton
||
343 mouseEvent
->button
== WidgetMouseEvent::eRightButton
)) {
344 aVisitor
.mDOMEvent
->StopPropagation();
349 case NS_MOUSE_ENTER_SYNTH
:
351 aVisitor
.mPresContext
->EventStateManager()->
352 SetContentState(this, NS_EVENT_STATE_HOVER
);
353 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
357 // XXX this doesn't seem to do anything yet
358 case NS_MOUSE_EXIT_SYNTH
:
360 aVisitor
.mPresContext
->EventStateManager()->
361 SetContentState(nullptr, NS_EVENT_STATE_HOVER
);
362 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
369 if (aVisitor
.mItemFlags
& NS_OUTER_ACTIVATE_EVENT
) {
370 if (mForm
&& (mType
== NS_FORM_BUTTON_SUBMIT
||
371 mType
== NS_FORM_BUTTON_RESET
)) {
372 InternalFormEvent
event(true,
373 (mType
== NS_FORM_BUTTON_RESET
) ? NS_FORM_RESET
: NS_FORM_SUBMIT
);
374 event
.originator
= this;
375 nsEventStatus status
= nsEventStatus_eIgnore
;
377 nsCOMPtr
<nsIPresShell
> presShell
=
378 aVisitor
.mPresContext
->GetPresShell();
379 // If |nsIPresShell::Destroy| has been called due to
380 // handling the event, the pres context will return
381 // a null pres shell. See bug 125624.
383 // Using presShell to dispatch the event. It makes sure that
384 // event is not handled if the window is being destroyed.
385 if (presShell
&& (event
.message
!= NS_FORM_SUBMIT
||
386 mForm
->HasAttr(kNameSpaceID_None
, nsGkAtoms::novalidate
) ||
387 // We know the element is a submit control, if this check is moved,
388 // make sure formnovalidate is used only if it's a submit control.
389 HasAttr(kNameSpaceID_None
, nsGkAtoms::formnovalidate
) ||
390 mForm
->CheckValidFormSubmission())) {
391 // TODO: removing this code and have the submit event sent by the form
393 // Hold a strong ref while dispatching
394 nsRefPtr
<HTMLFormElement
> form(mForm
);
395 presShell
->HandleDOMEventWithTarget(mForm
, &event
, &status
);
396 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
400 } else if ((aVisitor
.mItemFlags
& NS_IN_SUBMIT_CLICK
) && mForm
) {
401 // Tell the form to flush a possible pending submission.
402 // the reason is that the script returned false (the event was
403 // not ignored) so if there is a stored submission, it needs to
404 // be submitted immediatelly.
405 // Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
406 mForm
->FlushPendingSubmission();
413 HTMLButtonElement::BindToTree(nsIDocument
* aDocument
, nsIContent
* aParent
,
414 nsIContent
* aBindingParent
,
415 bool aCompileEventHandlers
)
418 nsGenericHTMLFormElementWithState::BindToTree(aDocument
, aParent
, aBindingParent
,
419 aCompileEventHandlers
);
420 NS_ENSURE_SUCCESS(rv
, rv
);
422 // Update our state; we may now be the default submit element
429 HTMLButtonElement::UnbindFromTree(bool aDeep
, bool aNullParent
)
431 nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep
, aNullParent
);
433 // Update our state; we may no longer be the default submit element
438 HTMLButtonElement::Reset()
444 HTMLButtonElement::SubmitNamesValues(nsFormSubmission
* aFormSubmission
)
447 // We only submit if we were the button pressed
449 if (aFormSubmission
->GetOriginatingElement() != this) {
453 // Disabled elements don't submit
459 // Get the name (if no name, no submit)
462 GetAttr(kNameSpaceID_None
, nsGkAtoms::name
, name
);
463 if (name
.IsEmpty()) {
471 nsresult rv
= GetValue(value
);
479 return aFormSubmission
->AddNameValuePair(name
, value
);
483 HTMLButtonElement::DoneCreatingElement()
485 if (!mInhibitStateRestoration
) {
486 nsresult rv
= GenerateStateKey();
487 if (NS_SUCCEEDED(rv
)) {
488 RestoreFormControlState();
494 HTMLButtonElement::BeforeSetAttr(int32_t aNameSpaceID
, nsIAtom
* aName
,
495 const nsAttrValueOrString
* aValue
,
498 if (aNotify
&& aName
== nsGkAtoms::disabled
&&
499 aNameSpaceID
== kNameSpaceID_None
) {
500 mDisabledChanged
= true;
503 return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID
, aName
,
508 HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID
, nsIAtom
* aName
,
509 const nsAttrValue
* aValue
, bool aNotify
)
511 if (aNameSpaceID
== kNameSpaceID_None
) {
512 if (aName
== nsGkAtoms::type
) {
514 mType
= kButtonDefaultType
->value
;
518 if (aName
== nsGkAtoms::type
|| aName
== nsGkAtoms::disabled
) {
519 UpdateBarredFromConstraintValidation();
520 UpdateState(aNotify
);
524 return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID
, aName
,
529 HTMLButtonElement::SaveState()
531 if (!mDisabledChanged
) {
535 nsPresState
* state
= GetPrimaryPresState();
537 // We do not want to save the real disabled state but the disabled
539 state
->SetDisabled(HasAttr(kNameSpaceID_None
, nsGkAtoms::disabled
));
546 HTMLButtonElement::RestoreState(nsPresState
* aState
)
548 if (aState
&& aState
->IsDisabledSet()) {
549 SetDisabled(aState
->GetDisabled());
556 HTMLButtonElement::IntrinsicState() const
558 EventStates state
= nsGenericHTMLFormElementWithState::IntrinsicState();
560 if (IsCandidateForConstraintValidation()) {
562 state
|= NS_EVENT_STATE_VALID
;
563 if (!mForm
|| !mForm
->HasAttr(kNameSpaceID_None
, nsGkAtoms::novalidate
)) {
564 state
|= NS_EVENT_STATE_MOZ_UI_VALID
;
567 state
|= NS_EVENT_STATE_INVALID
;
568 if (!mForm
|| !mForm
->HasAttr(kNameSpaceID_None
, nsGkAtoms::novalidate
)) {
569 state
|= NS_EVENT_STATE_MOZ_UI_INVALID
;
574 if (mForm
&& !mForm
->GetValidity() && IsSubmitControl()) {
575 state
|= NS_EVENT_STATE_MOZ_SUBMITINVALID
;
582 HTMLButtonElement::WrapNode(JSContext
* aCx
)
584 return HTMLButtonElementBinding::Wrap(aCx
, this);
588 } // namespace mozilla