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