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