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