Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/dom/HTMLButtonElement.h"
8 :
9 : #include "HTMLFormSubmissionConstants.h"
10 : #include "mozilla/dom/HTMLButtonElementBinding.h"
11 : #include "mozilla/dom/HTMLFormSubmission.h"
12 : #include "nsIDOMHTMLFormElement.h"
13 : #include "nsAttrValueInlines.h"
14 : #include "nsGkAtoms.h"
15 : #include "nsIPresShell.h"
16 : #include "nsStyleConsts.h"
17 : #include "nsPresContext.h"
18 : #include "nsIFormControl.h"
19 : #include "nsIURL.h"
20 : #include "nsIFrame.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"
33 : #include "nsError.h"
34 : #include "nsFocusManager.h"
35 : #include "mozilla/dom/HTMLFormElement.h"
36 : #include "mozAutoDocUpdate.h"
37 :
38 : #define NS_IN_SUBMIT_CLICK (1 << 0)
39 : #define NS_OUTER_ACTIVATE_EVENT (1 << 1)
40 :
41 2 : NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Button)
42 :
43 : namespace mozilla {
44 : namespace dom {
45 :
46 : static const nsAttrValue::EnumTable kButtonTypeTable[] = {
47 : { "button", NS_FORM_BUTTON_BUTTON },
48 : { "reset", NS_FORM_BUTTON_RESET },
49 : { "submit", NS_FORM_BUTTON_SUBMIT },
50 : { nullptr, 0 }
51 : };
52 :
53 : // Default type is 'submit'.
54 : static const nsAttrValue::EnumTable* kButtonDefaultType = &kButtonTypeTable[2];
55 :
56 :
57 : // Construction, destruction
58 1 : HTMLButtonElement::HTMLButtonElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
59 1 : FromParser aFromParser)
60 1 : : nsGenericHTMLFormElementWithState(aNodeInfo, kButtonDefaultType->value),
61 : mDisabledChanged(false),
62 : mInInternalActivate(false),
63 1 : mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT))
64 : {
65 : // Set up our default state: enabled
66 1 : AddStatesSilently(NS_EVENT_STATE_ENABLED);
67 1 : }
68 :
69 0 : HTMLButtonElement::~HTMLButtonElement()
70 : {
71 0 : }
72 :
73 : // nsISupports
74 :
75 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLButtonElement,
76 : nsGenericHTMLFormElementWithState,
77 : mValidity)
78 :
79 10 : NS_IMPL_ADDREF_INHERITED(HTMLButtonElement, Element)
80 8 : NS_IMPL_RELEASE_INHERITED(HTMLButtonElement, Element)
81 :
82 :
83 : // QueryInterface implementation for HTMLButtonElement
84 5 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLButtonElement)
85 4 : NS_INTERFACE_TABLE_INHERITED(HTMLButtonElement,
86 : nsIDOMHTMLButtonElement,
87 : nsIConstraintValidation)
88 4 : NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
89 :
90 : // nsIConstraintValidation
91 0 : NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLButtonElement)
92 :
93 : NS_IMETHODIMP
94 0 : HTMLButtonElement::SetCustomValidity(const nsAString& aError)
95 : {
96 0 : nsIConstraintValidation::SetCustomValidity(aError);
97 :
98 0 : UpdateState(true);
99 :
100 0 : return NS_OK;
101 : }
102 :
103 : void
104 0 : HTMLButtonElement::UpdateBarredFromConstraintValidation()
105 : {
106 0 : SetBarredFromConstraintValidation(mType == NS_FORM_BUTTON_BUTTON ||
107 0 : mType == NS_FORM_BUTTON_RESET ||
108 0 : IsDisabled());
109 0 : }
110 :
111 : void
112 0 : HTMLButtonElement::FieldSetDisabledChanged(bool aNotify)
113 : {
114 0 : UpdateBarredFromConstraintValidation();
115 :
116 0 : nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
117 0 : }
118 :
119 : // nsIDOMHTMLButtonElement
120 :
121 0 : NS_IMPL_ELEMENT_CLONE(HTMLButtonElement)
122 :
123 :
124 : // nsIDOMHTMLButtonElement
125 :
126 : NS_IMETHODIMP
127 0 : HTMLButtonElement::GetForm(nsIDOMHTMLFormElement** aForm)
128 : {
129 0 : return nsGenericHTMLFormElementWithState::GetForm(aForm);
130 : }
131 :
132 0 : NS_IMPL_BOOL_ATTR(HTMLButtonElement, Autofocus, autofocus)
133 0 : NS_IMPL_BOOL_ATTR(HTMLButtonElement, Disabled, disabled)
134 0 : NS_IMPL_ACTION_ATTR(HTMLButtonElement, FormAction, formaction)
135 0 : NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLButtonElement, FormEnctype, formenctype,
136 : "", kFormDefaultEnctype->tag)
137 0 : NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLButtonElement, FormMethod, formmethod,
138 : "", kFormDefaultMethod->tag)
139 0 : NS_IMPL_BOOL_ATTR(HTMLButtonElement, FormNoValidate, formnovalidate)
140 0 : NS_IMPL_STRING_ATTR(HTMLButtonElement, FormTarget, formtarget)
141 0 : NS_IMPL_STRING_ATTR(HTMLButtonElement, Name, name)
142 0 : NS_IMPL_STRING_ATTR(HTMLButtonElement, Value, value)
143 0 : NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLButtonElement, Type, type,
144 : kButtonDefaultType->tag)
145 :
146 : int32_t
147 0 : HTMLButtonElement::TabIndexDefault()
148 : {
149 0 : return 0;
150 : }
151 :
152 : bool
153 0 : HTMLButtonElement::IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex)
154 : {
155 0 : if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
156 0 : return true;
157 : }
158 :
159 0 : *aIsFocusable =
160 : #ifdef XP_MACOSX
161 : (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
162 : #endif
163 0 : !IsDisabled();
164 :
165 0 : return false;
166 : }
167 :
168 : bool
169 2 : HTMLButtonElement::ParseAttribute(int32_t aNamespaceID,
170 : nsIAtom* aAttribute,
171 : const nsAString& aValue,
172 : nsAttrValue& aResult)
173 : {
174 2 : if (aNamespaceID == kNameSpaceID_None) {
175 2 : if (aAttribute == nsGkAtoms::type) {
176 0 : return aResult.ParseEnumValue(aValue, kButtonTypeTable, false,
177 0 : kButtonDefaultType);
178 : }
179 :
180 2 : if (aAttribute == nsGkAtoms::formmethod) {
181 0 : return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
182 : }
183 2 : if (aAttribute == nsGkAtoms::formenctype) {
184 0 : return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
185 : }
186 : }
187 :
188 2 : return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
189 2 : aResult);
190 : }
191 :
192 : bool
193 0 : HTMLButtonElement::IsDisabledForEvents(EventMessage aMessage)
194 : {
195 0 : nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
196 0 : nsIFrame* formFrame = do_QueryFrame(formControlFrame);
197 0 : return IsElementDisabledForEvents(aMessage, formFrame);
198 : }
199 :
200 : nsresult
201 0 : HTMLButtonElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
202 : {
203 0 : aVisitor.mCanHandle = false;
204 0 : if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
205 0 : return NS_OK;
206 : }
207 :
208 : // Track whether we're in the outermost Dispatch invocation that will
209 : // cause activation of the input. That is, if we're a click event, or a
210 : // DOMActivate that was dispatched directly, this will be set, but if we're
211 : // a DOMActivate dispatched from click handling, it will not be set.
212 0 : WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
213 : bool outerActivateEvent =
214 0 : ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
215 0 : (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
216 0 : !mInInternalActivate));
217 :
218 0 : if (outerActivateEvent) {
219 0 : aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
220 0 : if (mType == NS_FORM_BUTTON_SUBMIT && mForm) {
221 0 : aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
222 : // tell the form that we are about to enter a click handler.
223 : // that means that if there are scripted submissions, the
224 : // latest one will be deferred until after the exit point of the handler.
225 0 : mForm->OnSubmitClickBegin(this);
226 : }
227 : }
228 :
229 0 : return nsGenericHTMLElement::GetEventTargetParent(aVisitor);
230 : }
231 :
232 : nsresult
233 0 : HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
234 : {
235 0 : nsresult rv = NS_OK;
236 0 : if (!aVisitor.mPresContext) {
237 0 : return rv;
238 : }
239 :
240 0 : if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
241 0 : WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
242 0 : if (mouseEvent && mouseEvent->IsLeftClickEvent()) {
243 : // DOMActive event should be trusted since the activation is actually
244 : // occurred even if the cause is an untrusted click event.
245 0 : InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
246 0 : actEvent.mDetail = 1;
247 :
248 0 : nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
249 0 : if (shell) {
250 0 : nsEventStatus status = nsEventStatus_eIgnore;
251 0 : mInInternalActivate = true;
252 0 : shell->HandleDOMEventWithTarget(this, &actEvent, &status);
253 0 : mInInternalActivate = false;
254 :
255 : // If activate is cancelled, we must do the same as when click is
256 : // cancelled (revert the checkbox to its original value).
257 0 : if (status == nsEventStatus_eConsumeNoDefault) {
258 0 : aVisitor.mEventStatus = status;
259 : }
260 : }
261 : }
262 : }
263 :
264 : // mForm is null if the event handler removed us from the document (bug 194582).
265 0 : if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
266 : // tell the form that we are about to exit a click handler
267 : // so the form knows not to defer subsequent submissions
268 : // the pending ones that were created during the handler
269 : // will be flushed or forgoten.
270 0 : mForm->OnSubmitClickEnd();
271 : }
272 :
273 0 : if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
274 0 : switch (aVisitor.mEvent->mMessage) {
275 : case eKeyPress:
276 : case eKeyUp:
277 : {
278 : // For backwards compat, trigger buttons with space or enter
279 : // (bug 25300)
280 0 : WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
281 0 : if ((keyEvent->mKeyCode == NS_VK_RETURN &&
282 0 : eKeyPress == aVisitor.mEvent->mMessage) ||
283 0 : (keyEvent->mKeyCode == NS_VK_SPACE &&
284 0 : eKeyUp == aVisitor.mEvent->mMessage)) {
285 0 : DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(),
286 0 : aVisitor.mPresContext);
287 0 : aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
288 : }
289 : }
290 0 : break;
291 :
292 : default:
293 0 : break;
294 : }
295 0 : if (aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) {
296 0 : if (mForm && (mType == NS_FORM_BUTTON_SUBMIT ||
297 0 : mType == NS_FORM_BUTTON_RESET)) {
298 : InternalFormEvent event(true,
299 0 : (mType == NS_FORM_BUTTON_RESET) ? eFormReset : eFormSubmit);
300 0 : event.mOriginator = this;
301 0 : nsEventStatus status = nsEventStatus_eIgnore;
302 :
303 : nsCOMPtr<nsIPresShell> presShell =
304 0 : aVisitor.mPresContext->GetPresShell();
305 : // If |nsIPresShell::Destroy| has been called due to
306 : // handling the event, the pres context will return
307 : // a null pres shell. See bug 125624.
308 : //
309 : // Using presShell to dispatch the event. It makes sure that
310 : // event is not handled if the window is being destroyed.
311 0 : if (presShell && (event.mMessage != eFormSubmit ||
312 0 : mForm->SubmissionCanProceed(this))) {
313 : // TODO: removing this code and have the submit event sent by the form
314 : // see bug 592124.
315 : // Hold a strong ref while dispatching
316 0 : RefPtr<HTMLFormElement> form(mForm);
317 0 : presShell->HandleDOMEventWithTarget(form, &event, &status);
318 0 : aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
319 : }
320 : }
321 : }
322 0 : } else if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
323 : // Tell the form to flush a possible pending submission.
324 : // the reason is that the script returned false (the event was
325 : // not ignored) so if there is a stored submission, it needs to
326 : // be submitted immediatelly.
327 : // Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
328 0 : mForm->FlushPendingSubmission();
329 : } //if
330 :
331 0 : return rv;
332 : }
333 :
334 : nsresult
335 1 : HTMLButtonElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
336 : nsIContent* aBindingParent,
337 : bool aCompileEventHandlers)
338 : {
339 : nsresult rv =
340 1 : nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent, aBindingParent,
341 1 : aCompileEventHandlers);
342 1 : NS_ENSURE_SUCCESS(rv, rv);
343 :
344 : // Update our state; we may now be the default submit element
345 1 : UpdateState(false);
346 :
347 1 : return NS_OK;
348 : }
349 :
350 : void
351 0 : HTMLButtonElement::UnbindFromTree(bool aDeep, bool aNullParent)
352 : {
353 0 : nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
354 :
355 : // Update our state; we may no longer be the default submit element
356 0 : UpdateState(false);
357 0 : }
358 :
359 : NS_IMETHODIMP
360 0 : HTMLButtonElement::Reset()
361 : {
362 0 : return NS_OK;
363 : }
364 :
365 : NS_IMETHODIMP
366 0 : HTMLButtonElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
367 : {
368 : //
369 : // We only submit if we were the button pressed
370 : //
371 0 : if (aFormSubmission->GetOriginatingElement() != this) {
372 0 : return NS_OK;
373 : }
374 :
375 : // Disabled elements don't submit
376 0 : if (IsDisabled()) {
377 0 : return NS_OK;
378 : }
379 :
380 : //
381 : // Get the name (if no name, no submit)
382 : //
383 0 : nsAutoString name;
384 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
385 0 : if (name.IsEmpty()) {
386 0 : return NS_OK;
387 : }
388 :
389 : //
390 : // Get the value
391 : //
392 0 : nsAutoString value;
393 0 : nsresult rv = GetValue(value);
394 0 : if (NS_FAILED(rv)) {
395 0 : return rv;
396 : }
397 :
398 : //
399 : // Submit
400 : //
401 0 : return aFormSubmission->AddNameValuePair(name, value);
402 : }
403 :
404 : void
405 0 : HTMLButtonElement::DoneCreatingElement()
406 : {
407 0 : if (!mInhibitStateRestoration) {
408 0 : nsresult rv = GenerateStateKey();
409 0 : if (NS_SUCCEEDED(rv)) {
410 0 : RestoreFormControlState();
411 : }
412 : }
413 0 : }
414 :
415 : nsresult
416 2 : HTMLButtonElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
417 : const nsAttrValueOrString* aValue,
418 : bool aNotify)
419 : {
420 2 : if (aNotify && aName == nsGkAtoms::disabled &&
421 : aNameSpaceID == kNameSpaceID_None) {
422 0 : mDisabledChanged = true;
423 : }
424 :
425 2 : return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
426 2 : aValue, aNotify);
427 : }
428 :
429 : nsresult
430 2 : HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
431 : const nsAttrValue* aValue,
432 : const nsAttrValue* aOldValue, bool aNotify)
433 : {
434 2 : if (aNameSpaceID == kNameSpaceID_None) {
435 2 : if (aName == nsGkAtoms::type) {
436 0 : if (aValue) {
437 0 : mType = aValue->GetEnumValue();
438 : } else {
439 0 : mType = kButtonDefaultType->value;
440 : }
441 : }
442 :
443 2 : if (aName == nsGkAtoms::type || aName == nsGkAtoms::disabled) {
444 0 : UpdateBarredFromConstraintValidation();
445 : }
446 : }
447 :
448 2 : return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
449 : aValue, aOldValue,
450 2 : aNotify);
451 : }
452 :
453 : NS_IMETHODIMP
454 0 : HTMLButtonElement::SaveState()
455 : {
456 0 : if (!mDisabledChanged) {
457 0 : return NS_OK;
458 : }
459 :
460 0 : nsPresState* state = GetPrimaryPresState();
461 0 : if (state) {
462 : // We do not want to save the real disabled state but the disabled
463 : // attribute.
464 0 : state->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
465 : }
466 :
467 0 : return NS_OK;
468 : }
469 :
470 : bool
471 0 : HTMLButtonElement::RestoreState(nsPresState* aState)
472 : {
473 0 : if (aState && aState->IsDisabledSet() && !aState->GetDisabled()) {
474 0 : SetDisabled(false);
475 : }
476 :
477 0 : return false;
478 : }
479 :
480 : EventStates
481 3 : HTMLButtonElement::IntrinsicState() const
482 : {
483 3 : EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
484 :
485 3 : if (IsCandidateForConstraintValidation()) {
486 3 : if (IsValid()) {
487 3 : state |= NS_EVENT_STATE_VALID;
488 3 : if (!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
489 3 : state |= NS_EVENT_STATE_MOZ_UI_VALID;
490 : }
491 : } else {
492 0 : state |= NS_EVENT_STATE_INVALID;
493 0 : if (!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
494 0 : state |= NS_EVENT_STATE_MOZ_UI_INVALID;
495 : }
496 : }
497 : }
498 :
499 3 : if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
500 0 : state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
501 : }
502 :
503 3 : return state;
504 : }
505 :
506 : JSObject*
507 0 : HTMLButtonElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
508 : {
509 0 : return HTMLButtonElementBinding::Wrap(aCx, this, aGivenProto);
510 : }
511 :
512 : } // namespace dom
513 : } // namespace mozilla
|