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 "nsAttrValueInlines.h"
13 #include "nsGkAtoms.h"
14 #include "nsStyleConsts.h"
15 #include "nsPresContext.h"
16 #include "nsIFormControl.h"
17 #include "nsIFrame.h"
18 #include "nsIFormControlFrame.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/ContentEvents.h"
21 #include "mozilla/EventDispatcher.h"
22 #include "mozilla/EventStateManager.h"
23 #include "mozilla/EventStates.h"
24 #include "mozilla/MouseEvents.h"
25 #include "mozilla/PresShell.h"
26 #include "mozilla/TextEvents.h"
27 #include "nsUnicharUtils.h"
28 #include "nsLayoutUtils.h"
29 #include "mozilla/PresState.h"
30 #include "nsError.h"
31 #include "nsFocusManager.h"
32 #include "mozilla/dom/HTMLFormElement.h"
33 #include "mozAutoDocUpdate.h"
34 
35 #define NS_IN_SUBMIT_CLICK (1 << 0)
36 #define NS_OUTER_ACTIVATE_EVENT (1 << 1)
37 
38 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Button)
39 
40 namespace mozilla::dom {
41 
42 static const nsAttrValue::EnumTable kButtonTypeTable[] = {
43     {"button", FormControlType::ButtonButton},
44     {"reset", FormControlType::ButtonReset},
45     {"submit", FormControlType::ButtonSubmit},
46     {nullptr, 0}};
47 
48 // Default type is 'submit'.
49 static const nsAttrValue::EnumTable* kButtonDefaultType = &kButtonTypeTable[2];
50 
51 // Construction, destruction
HTMLButtonElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,FromParser aFromParser)52 HTMLButtonElement::HTMLButtonElement(
53     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
54     FromParser aFromParser)
55     : nsGenericHTMLFormElementWithState(
56           std::move(aNodeInfo), aFromParser,
57           FormControlType(kButtonDefaultType->value)),
58       mDisabledChanged(false),
59       mInInternalActivate(false),
60       mInhibitStateRestoration(aFromParser & FROM_PARSER_FRAGMENT) {
61   // Set up our default state: enabled
62   AddStatesSilently(NS_EVENT_STATE_ENABLED);
63 }
64 
65 HTMLButtonElement::~HTMLButtonElement() = default;
66 
67 // nsISupports
68 
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLButtonElement,nsGenericHTMLFormElementWithState,mValidity)69 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLButtonElement,
70                                    nsGenericHTMLFormElementWithState, mValidity)
71 
72 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLButtonElement,
73                                              nsGenericHTMLFormElementWithState,
74                                              nsIConstraintValidation)
75 
76 void HTMLButtonElement::SetCustomValidity(const nsAString& aError) {
77   nsIConstraintValidation::SetCustomValidity(aError);
78 
79   UpdateState(true);
80 }
81 
UpdateBarredFromConstraintValidation()82 void HTMLButtonElement::UpdateBarredFromConstraintValidation() {
83   SetBarredFromConstraintValidation(mType == FormControlType::ButtonButton ||
84                                     mType == FormControlType::ButtonReset ||
85                                     IsDisabled());
86 }
87 
FieldSetDisabledChanged(bool aNotify)88 void HTMLButtonElement::FieldSetDisabledChanged(bool aNotify) {
89   // FieldSetDisabledChanged *has* to be called *before*
90   // UpdateBarredFromConstraintValidation, because the latter depends on our
91   // disabled state.
92   nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
93 
94   UpdateBarredFromConstraintValidation();
95   UpdateState(aNotify);
96 }
97 
NS_IMPL_ELEMENT_CLONE(HTMLButtonElement)98 NS_IMPL_ELEMENT_CLONE(HTMLButtonElement)
99 
100 void HTMLButtonElement::GetFormEnctype(nsAString& aFormEncType) {
101   GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag,
102               aFormEncType);
103 }
104 
GetFormMethod(nsAString & aFormMethod)105 void HTMLButtonElement::GetFormMethod(nsAString& aFormMethod) {
106   GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aFormMethod);
107 }
108 
GetType(nsAString & aType)109 void HTMLButtonElement::GetType(nsAString& aType) {
110   GetEnumAttr(nsGkAtoms::type, kButtonDefaultType->tag, aType);
111 }
112 
TabIndexDefault()113 int32_t HTMLButtonElement::TabIndexDefault() { return 0; }
114 
IsHTMLFocusable(bool aWithMouse,bool * aIsFocusable,int32_t * aTabIndex)115 bool HTMLButtonElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
116                                         int32_t* aTabIndex) {
117   if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(
118           aWithMouse, aIsFocusable, aTabIndex)) {
119     return true;
120   }
121 
122   *aIsFocusable = IsFormControlDefaultFocusable(aWithMouse) && !IsDisabled();
123 
124   return false;
125 }
126 
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)127 bool HTMLButtonElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
128                                        const nsAString& aValue,
129                                        nsIPrincipal* aMaybeScriptedPrincipal,
130                                        nsAttrValue& aResult) {
131   if (aNamespaceID == kNameSpaceID_None) {
132     if (aAttribute == nsGkAtoms::type) {
133       return aResult.ParseEnumValue(aValue, kButtonTypeTable, false,
134                                     kButtonDefaultType);
135     }
136 
137     if (aAttribute == nsGkAtoms::formmethod) {
138       if (StaticPrefs::dom_dialog_element_enabled() || IsInChromeDocument()) {
139         return aResult.ParseEnumValue(aValue, kFormMethodTableDialogEnabled,
140                                       false);
141       }
142       return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
143     }
144     if (aAttribute == nsGkAtoms::formenctype) {
145       return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
146     }
147   }
148 
149   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
150                                               aMaybeScriptedPrincipal, aResult);
151 }
152 
IsDisabledForEvents(WidgetEvent * aEvent)153 bool HTMLButtonElement::IsDisabledForEvents(WidgetEvent* aEvent) {
154   nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
155   nsIFrame* formFrame = do_QueryFrame(formControlFrame);
156   return IsElementDisabledForEvents(aEvent, formFrame);
157 }
158 
GetEventTargetParent(EventChainPreVisitor & aVisitor)159 void HTMLButtonElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
160   aVisitor.mCanHandle = false;
161 
162   if (IsDisabledForEvents(aVisitor.mEvent)) {
163     return;
164   }
165 
166   // Track whether we're in the outermost Dispatch invocation that will
167   // cause activation of the input.  That is, if we're a click event, or a
168   // DOMActivate that was dispatched directly, this will be set, but if we're
169   // a DOMActivate dispatched from click handling, it will not be set.
170   WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
171   bool outerActivateEvent =
172       ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
173        (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
174         !mInInternalActivate && aVisitor.mEvent->mOriginalTarget == this));
175 
176   if (outerActivateEvent) {
177     aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
178     if (mType == FormControlType::ButtonSubmit && mForm &&
179         !aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented) {
180       aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented = true;
181       aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
182       // tell the form that we are about to enter a click handler.
183       // that means that if there are scripted submissions, the
184       // latest one will be deferred until after the exit point of the handler.
185       mForm->OnSubmitClickBegin(this);
186     }
187   }
188 
189   nsGenericHTMLElement::GetEventTargetParent(aVisitor);
190 }
191 
PostHandleEvent(EventChainPostVisitor & aVisitor)192 nsresult HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
193   nsresult rv = NS_OK;
194   if (!aVisitor.mPresContext) {
195     return rv;
196   }
197 
198   if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
199     WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
200     if (mouseEvent && mouseEvent->IsLeftClickEvent()) {
201       // DOMActive event should be trusted since the activation is actually
202       // occurred even if the cause is an untrusted click event.
203       InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
204       actEvent.mDetail = 1;
205 
206       if (RefPtr<PresShell> presShell = aVisitor.mPresContext->GetPresShell()) {
207         nsEventStatus status = nsEventStatus_eIgnore;
208         mInInternalActivate = true;
209         presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
210         mInInternalActivate = false;
211 
212         // If activate is cancelled, we must do the same as when click is
213         // cancelled (revert the checkbox to its original value).
214         if (status == nsEventStatus_eConsumeNoDefault) {
215           aVisitor.mEventStatus = status;
216         }
217       }
218     }
219   }
220 
221   // mForm is null if the event handler removed us from the document (bug
222   // 194582).
223   if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
224     // tell the form that we are about to exit a click handler
225     // so the form knows not to defer subsequent submissions
226     // the pending ones that were created during the handler
227     // will be flushed or forgoten.
228     mForm->OnSubmitClickEnd();
229   }
230 
231   if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
232     HandleKeyboardActivation(aVisitor);
233     if (aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) {
234       if (mForm) {
235         // Hold a strong ref while dispatching
236         RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
237         if (mType == FormControlType::ButtonReset) {
238           form->MaybeReset(this);
239           aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
240         } else if (mType == FormControlType::ButtonSubmit) {
241           form->MaybeSubmit(this);
242           aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
243         }
244         // https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-button-state
245         // NS_FORM_BUTTON_BUTTON do nothing.
246       }
247     }
248   } else if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
249     // Tell the form to flush a possible pending submission.
250     // the reason is that the script returned false (the event was
251     // not ignored) so if there is a stored submission, it needs to
252     // be submitted immediatelly.
253     // Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
254     mForm->FlushPendingSubmission();
255   }  // if
256 
257   return rv;
258 }
259 
BindToTree(BindContext & aContext,nsINode & aParent)260 nsresult HTMLButtonElement::BindToTree(BindContext& aContext,
261                                        nsINode& aParent) {
262   nsresult rv =
263       nsGenericHTMLFormElementWithState::BindToTree(aContext, aParent);
264   NS_ENSURE_SUCCESS(rv, rv);
265 
266   // Update our state; we may now be the default submit element
267   UpdateState(false);
268 
269   return NS_OK;
270 }
271 
UnbindFromTree(bool aNullParent)272 void HTMLButtonElement::UnbindFromTree(bool aNullParent) {
273   nsGenericHTMLFormElementWithState::UnbindFromTree(aNullParent);
274 
275   // Update our state; we may no longer be the default submit element
276   UpdateState(false);
277 }
278 
279 NS_IMETHODIMP
Reset()280 HTMLButtonElement::Reset() { return NS_OK; }
281 
282 NS_IMETHODIMP
SubmitNamesValues(HTMLFormSubmission * aFormSubmission)283 HTMLButtonElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission) {
284   //
285   // We only submit if we were the button pressed
286   //
287   if (aFormSubmission->GetSubmitterElement() != this) {
288     return NS_OK;
289   }
290 
291   // Disabled elements don't submit
292   if (IsDisabled()) {
293     return NS_OK;
294   }
295 
296   //
297   // Get the name (if no name, no submit)
298   //
299   nsAutoString name;
300   GetHTMLAttr(nsGkAtoms::name, name);
301   if (name.IsEmpty()) {
302     return NS_OK;
303   }
304 
305   //
306   // Get the value
307   //
308   nsAutoString value;
309   GetHTMLAttr(nsGkAtoms::value, value);
310 
311   //
312   // Submit
313   //
314   return aFormSubmission->AddNameValuePair(name, value);
315 }
316 
DoneCreatingElement()317 void HTMLButtonElement::DoneCreatingElement() {
318   if (!mInhibitStateRestoration) {
319     GenerateStateKey();
320     RestoreFormControlState();
321   }
322 }
323 
BeforeSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValueOrString * aValue,bool aNotify)324 nsresult HTMLButtonElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
325                                           const nsAttrValueOrString* aValue,
326                                           bool aNotify) {
327   if (aNotify && aName == nsGkAtoms::disabled &&
328       aNameSpaceID == kNameSpaceID_None) {
329     mDisabledChanged = true;
330   }
331 
332   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
333                                                           aValue, aNotify);
334 }
335 
AfterSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)336 nsresult HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
337                                          const nsAttrValue* aValue,
338                                          const nsAttrValue* aOldValue,
339                                          nsIPrincipal* aSubjectPrincipal,
340                                          bool aNotify) {
341   if (aNameSpaceID == kNameSpaceID_None) {
342     if (aName == nsGkAtoms::type) {
343       if (aValue) {
344         mType = FormControlType(aValue->GetEnumValue());
345       } else {
346         mType = FormControlType(kButtonDefaultType->value);
347       }
348     }
349 
350     if (aName == nsGkAtoms::type || aName == nsGkAtoms::disabled) {
351       if (aName == nsGkAtoms::disabled) {
352         // This *has* to be called *before* validity state check because
353         // UpdateBarredFromConstraintValidation depends on our disabled state.
354         UpdateDisabledState(aNotify);
355       }
356 
357       UpdateBarredFromConstraintValidation();
358     }
359   }
360 
361   return nsGenericHTMLFormElementWithState::AfterSetAttr(
362       aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
363 }
364 
365 NS_IMETHODIMP
SaveState()366 HTMLButtonElement::SaveState() {
367   if (!mDisabledChanged) {
368     return NS_OK;
369   }
370 
371   PresState* state = GetPrimaryPresState();
372   if (state) {
373     // We do not want to save the real disabled state but the disabled
374     // attribute.
375     state->disabled() = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
376     state->disabledSet() = true;
377   }
378 
379   return NS_OK;
380 }
381 
RestoreState(PresState * aState)382 bool HTMLButtonElement::RestoreState(PresState* aState) {
383   if (aState && aState->disabledSet() && !aState->disabled()) {
384     SetDisabled(false, IgnoreErrors());
385   }
386 
387   return false;
388 }
389 
IntrinsicState() const390 EventStates HTMLButtonElement::IntrinsicState() const {
391   EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
392 
393   if (IsCandidateForConstraintValidation()) {
394     if (IsValid()) {
395       state |= NS_EVENT_STATE_VALID | NS_EVENT_STATE_MOZ_UI_VALID;
396     } else {
397       state |= NS_EVENT_STATE_INVALID | NS_EVENT_STATE_MOZ_UI_INVALID;
398     }
399   }
400 
401   if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
402     state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
403   }
404 
405   return state;
406 }
407 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)408 JSObject* HTMLButtonElement::WrapNode(JSContext* aCx,
409                                       JS::Handle<JSObject*> aGivenProto) {
410   return HTMLButtonElement_Binding::Wrap(aCx, this, aGivenProto);
411 }
412 
413 }  // namespace mozilla::dom
414