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/HTMLFormElement.h"
8
9 #include <utility>
10
11 #include "jsapi.h"
12 #include "mozilla/AutoRestore.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/BinarySearch.h"
15 #include "mozilla/Components.h"
16 #include "mozilla/ContentEvents.h"
17 #include "mozilla/EventDispatcher.h"
18 #include "mozilla/EventStates.h"
19 #include "mozilla/PresShell.h"
20 #include "mozilla/UniquePtr.h"
21 #include "mozilla/dom/BindContext.h"
22 #include "mozilla/dom/BrowsingContext.h"
23 #include "mozilla/dom/CustomEvent.h"
24 #include "mozilla/dom/Document.h"
25 #include "mozilla/dom/HTMLFormControlsCollection.h"
26 #include "mozilla/dom/HTMLFormElementBinding.h"
27 #include "mozilla/dom/nsCSPContext.h"
28 #include "mozilla/dom/nsCSPUtils.h"
29 #include "mozilla/dom/nsMixedContentBlocker.h"
30 #include "nsCOMArray.h"
31 #include "nsContentList.h"
32 #include "nsContentUtils.h"
33 #include "nsDocShell.h"
34 #include "nsDocShellLoadState.h"
35 #include "nsError.h"
36 #include "nsGkAtoms.h"
37 #include "nsHTMLDocument.h"
38 #include "nsIFormControlFrame.h"
39 #include "nsInterfaceHashtable.h"
40 #include "nsPresContext.h"
41 #include "nsQueryObject.h"
42 #include "nsStyleConsts.h"
43 #include "nsTArray.h"
44
45 // form submission
46 #include "HTMLFormSubmissionConstants.h"
47 #include "mozilla/dom/FormData.h"
48 #include "mozilla/dom/FormDataEvent.h"
49 #include "mozilla/dom/SubmitEvent.h"
50 #include "mozilla/Telemetry.h"
51 #include "mozilla/StaticPrefs_dom.h"
52 #include "mozilla/StaticPrefs_prompts.h"
53 #include "mozilla/StaticPrefs_signon.h"
54 #include "nsCategoryManagerUtils.h"
55 #include "nsIContentInlines.h"
56 #include "nsISimpleEnumerator.h"
57 #include "nsRange.h"
58 #include "nsIScriptError.h"
59 #include "nsIScriptSecurityManager.h"
60 #include "nsNetUtil.h"
61 #include "nsIInterfaceRequestorUtils.h"
62 #include "nsIDocShell.h"
63 #include "nsIPromptService.h"
64 #include "nsISecurityUITelemetry.h"
65 #include "nsIStringBundle.h"
66
67 // radio buttons
68 #include "mozilla/dom/HTMLInputElement.h"
69 #include "nsIRadioVisitor.h"
70 #include "RadioNodeList.h"
71
72 #include "nsLayoutUtils.h"
73
74 #include "mozAutoDocUpdate.h"
75 #include "nsIHTMLCollection.h"
76
77 #include "nsIConstraintValidation.h"
78
79 #include "nsSandboxFlags.h"
80
81 // images
82 #include "mozilla/dom/HTMLImageElement.h"
83 #include "mozilla/dom/HTMLButtonElement.h"
84
85 // construction, destruction
86 NS_IMPL_NS_NEW_HTML_ELEMENT(Form)
87
88 namespace mozilla::dom {
89
90 static const uint8_t NS_FORM_AUTOCOMPLETE_ON = 1;
91 static const uint8_t NS_FORM_AUTOCOMPLETE_OFF = 0;
92
93 static const nsAttrValue::EnumTable kFormAutocompleteTable[] = {
94 {"on", NS_FORM_AUTOCOMPLETE_ON},
95 {"off", NS_FORM_AUTOCOMPLETE_OFF},
96 {nullptr, 0}};
97 // Default autocomplete value is 'on'.
98 static const nsAttrValue::EnumTable* kFormDefaultAutocomplete =
99 &kFormAutocompleteTable[0];
100
HTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)101 HTMLFormElement::HTMLFormElement(
102 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
103 : nsGenericHTMLElement(std::move(aNodeInfo)),
104 mControls(new HTMLFormControlsCollection(this)),
105 mPendingSubmission(nullptr),
106 mDefaultSubmitElement(nullptr),
107 mFirstSubmitInElements(nullptr),
108 mFirstSubmitNotInElements(nullptr),
109 mImageNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
110 mPastNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
111 mSubmitPopupState(PopupBlocker::openAbused),
112 mInvalidElementsCount(0),
113 mFormNumber(-1),
114 mGeneratingSubmit(false),
115 mGeneratingReset(false),
116 mDeferSubmission(false),
117 mNotifiedObservers(false),
118 mNotifiedObserversResult(false),
119 mEverTriedInvalidSubmit(false),
120 mIsConstructingEntryList(false),
121 mIsFiringSubmissionEvents(false) {
122 // We start out valid.
123 AddStatesSilently(NS_EVENT_STATE_VALID);
124 }
125
~HTMLFormElement()126 HTMLFormElement::~HTMLFormElement() {
127 if (mControls) {
128 mControls->DropFormReference();
129 }
130
131 Clear();
132 }
133
134 // nsISupports
135
136 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormElement)
137
138 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLFormElement,
139 nsGenericHTMLElement)
140 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControls)
141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable)
142 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
143 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTargetContext)
144 RadioGroupManager::Traverse(tmp, cb);
145 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
146
147 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
148 nsGenericHTMLElement)
149 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTargetContext)
150 RadioGroupManager::Unlink(tmp);
151 tmp->Clear();
152 tmp->mExpandoAndGeneration.OwnerUnlinked();
153 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
154
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFormElement,nsGenericHTMLElement,nsIRadioGroupContainer)155 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFormElement,
156 nsGenericHTMLElement,
157 nsIRadioGroupContainer)
158
159 // EventTarget
160 void HTMLFormElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
161 if (mFormPasswordEventDispatcher == aEvent) {
162 mFormPasswordEventDispatcher = nullptr;
163 } else if (mFormPossibleUsernameEventDispatcher == aEvent) {
164 mFormPossibleUsernameEventDispatcher = nullptr;
165 }
166 }
167
NS_IMPL_ELEMENT_CLONE(HTMLFormElement)168 NS_IMPL_ELEMENT_CLONE(HTMLFormElement)
169
170 nsIHTMLCollection* HTMLFormElement::Elements() { return mControls; }
171
BeforeSetAttr(int32_t aNamespaceID,nsAtom * aName,const nsAttrValueOrString * aValue,bool aNotify)172 nsresult HTMLFormElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
173 const nsAttrValueOrString* aValue,
174 bool aNotify) {
175 if (aNamespaceID == kNameSpaceID_None) {
176 if (aName == nsGkAtoms::action || aName == nsGkAtoms::target) {
177 // Don't forget we've notified the password manager already if the
178 // page sets the action/target in the during submit. (bug 343182)
179 bool notifiedObservers = mNotifiedObservers;
180 ForgetCurrentSubmission();
181 mNotifiedObservers = notifiedObservers;
182 }
183 }
184
185 return nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName, aValue,
186 aNotify);
187 }
188
GetAutocomplete(nsAString & aValue)189 void HTMLFormElement::GetAutocomplete(nsAString& aValue) {
190 GetEnumAttr(nsGkAtoms::autocomplete, kFormDefaultAutocomplete->tag, aValue);
191 }
192
GetEnctype(nsAString & aValue)193 void HTMLFormElement::GetEnctype(nsAString& aValue) {
194 GetEnumAttr(nsGkAtoms::enctype, kFormDefaultEnctype->tag, aValue);
195 }
196
GetMethod(nsAString & aValue)197 void HTMLFormElement::GetMethod(nsAString& aValue) {
198 GetEnumAttr(nsGkAtoms::method, kFormDefaultMethod->tag, aValue);
199 }
200
201 // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit
MaybeSubmit(Element * aSubmitter)202 void HTMLFormElement::MaybeSubmit(Element* aSubmitter) {
203 #ifdef DEBUG
204 if (aSubmitter) {
205 nsCOMPtr<nsIFormControl> fc = do_QueryInterface(aSubmitter);
206 MOZ_ASSERT(fc);
207 MOZ_ASSERT(fc->IsSubmitControl(), "aSubmitter is not a submit control?");
208 }
209 #endif
210
211 // 1-4 of
212 // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit
213 Document* doc = GetComposedDoc();
214 if (mIsConstructingEntryList || !doc ||
215 (doc->GetSandboxFlags() & SANDBOXED_FORMS)) {
216 return;
217 }
218
219 // 6.1. If form's firing submission events is true, then return.
220 if (mIsFiringSubmissionEvents) {
221 return;
222 }
223
224 // 6.2. Set form's firing submission events to true.
225 AutoRestore<bool> resetFiringSubmissionEventsFlag(mIsFiringSubmissionEvents);
226 mIsFiringSubmissionEvents = true;
227
228 // 6.3. If the submitter element's no-validate state is false, then
229 // interactively validate the constraints of form and examine the result.
230 // If the result is negative (i.e., the constraint validation concluded
231 // that there were invalid fields and probably informed the user of this)
232 bool noValidateState =
233 HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate) ||
234 (aSubmitter &&
235 aSubmitter->HasAttr(kNameSpaceID_None, nsGkAtoms::formnovalidate));
236 if (!noValidateState && !CheckValidFormSubmission()) {
237 return;
238 }
239
240 // If |PresShell::Destroy| has been called due to handling the event the pres
241 // context will return a null pres shell. See bug 125624. Using presShell to
242 // dispatch the event. It makes sure that event is not handled if the window
243 // is being destroyed.
244 if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
245 SubmitEventInit init;
246 init.mBubbles = true;
247 init.mCancelable = true;
248 init.mSubmitter =
249 aSubmitter ? nsGenericHTMLElement::FromNode(aSubmitter) : nullptr;
250 RefPtr<SubmitEvent> event =
251 SubmitEvent::Constructor(this, u"submit"_ns, init);
252 event->SetTrusted(true);
253 nsEventStatus status = nsEventStatus_eIgnore;
254 presShell->HandleDOMEventWithTarget(this, event, &status);
255 }
256 }
257
MaybeReset(Element * aSubmitter)258 void HTMLFormElement::MaybeReset(Element* aSubmitter) {
259 // If |PresShell::Destroy| has been called due to handling the event the pres
260 // context will return a null pres shell. See bug 125624. Using presShell to
261 // dispatch the event. It makes sure that event is not handled if the window
262 // is being destroyed.
263 if (RefPtr<PresShell> presShell = OwnerDoc()->GetPresShell()) {
264 InternalFormEvent event(true, eFormReset);
265 event.mOriginator = aSubmitter;
266 nsEventStatus status = nsEventStatus_eIgnore;
267 presShell->HandleDOMEventWithTarget(this, &event, &status);
268 }
269 }
270
Submit(ErrorResult & aRv)271 void HTMLFormElement::Submit(ErrorResult& aRv) {
272 // Send the submit event
273 if (mPendingSubmission) {
274 // aha, we have a pending submission that was not flushed
275 // (this happens when form.submit() is called twice)
276 // we have to delete it and build a new one since values
277 // might have changed inbetween (we emulate IE here, that's all)
278 mPendingSubmission = nullptr;
279 }
280
281 aRv = DoSubmit();
282 }
283
284 // https://html.spec.whatwg.org/multipage/forms.html#dom-form-requestsubmit
RequestSubmit(nsGenericHTMLElement * aSubmitter,ErrorResult & aRv)285 void HTMLFormElement::RequestSubmit(nsGenericHTMLElement* aSubmitter,
286 ErrorResult& aRv) {
287 // 1. If submitter is not null, then:
288 if (aSubmitter) {
289 nsCOMPtr<nsIFormControl> fc = do_QueryObject(aSubmitter);
290
291 // 1.1. If submitter is not a submit button, then throw a TypeError.
292 if (!fc || !fc->IsSubmitControl()) {
293 aRv.ThrowTypeError("The submitter is not a submit button.");
294 return;
295 }
296
297 // 1.2. If submitter's form owner is not this form element, then throw a
298 // "NotFoundError" DOMException.
299 if (fc->GetForm() != this) {
300 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
301 return;
302 }
303 }
304
305 // 2. Otherwise, set submitter to this form element.
306 // 3. Submit this form element, from submitter.
307 MaybeSubmit(aSubmitter);
308 }
309
Reset()310 void HTMLFormElement::Reset() {
311 InternalFormEvent event(true, eFormReset);
312 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), nullptr, &event);
313 }
314
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)315 bool HTMLFormElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
316 const nsAString& aValue,
317 nsIPrincipal* aMaybeScriptedPrincipal,
318 nsAttrValue& aResult) {
319 if (aNamespaceID == kNameSpaceID_None) {
320 if (aAttribute == nsGkAtoms::method) {
321 if (StaticPrefs::dom_dialog_element_enabled() || IsInChromeDocument()) {
322 return aResult.ParseEnumValue(aValue, kFormMethodTableDialogEnabled,
323 false);
324 }
325 return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
326 }
327 if (aAttribute == nsGkAtoms::enctype) {
328 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
329 }
330 if (aAttribute == nsGkAtoms::autocomplete) {
331 return aResult.ParseEnumValue(aValue, kFormAutocompleteTable, false);
332 }
333 }
334
335 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
336 aMaybeScriptedPrincipal, aResult);
337 }
338
BindToTree(BindContext & aContext,nsINode & aParent)339 nsresult HTMLFormElement::BindToTree(BindContext& aContext, nsINode& aParent) {
340 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
341 NS_ENSURE_SUCCESS(rv, rv);
342
343 if (IsInUncomposedDoc() && aContext.OwnerDoc().IsHTMLOrXHTML()) {
344 aContext.OwnerDoc().AsHTMLDocument()->AddedForm();
345 }
346
347 return rv;
348 }
349
350 template <typename T>
MarkOrphans(const nsTArray<T * > & aArray)351 static void MarkOrphans(const nsTArray<T*>& aArray) {
352 uint32_t length = aArray.Length();
353 for (uint32_t i = 0; i < length; ++i) {
354 aArray[i]->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
355 }
356 }
357
CollectOrphans(nsINode * aRemovalRoot,const nsTArray<nsGenericHTMLFormElement * > & aArray,HTMLFormElement * aThisForm)358 static void CollectOrphans(nsINode* aRemovalRoot,
359 const nsTArray<nsGenericHTMLFormElement*>& aArray
360 #ifdef DEBUG
361 ,
362 HTMLFormElement* aThisForm
363 #endif
364 ) {
365 // Put a script blocker around all the notifications we're about to do.
366 nsAutoScriptBlocker scriptBlocker;
367
368 // Walk backwards so that if we remove elements we can just keep iterating
369 uint32_t length = aArray.Length();
370 for (uint32_t i = length; i > 0; --i) {
371 nsGenericHTMLFormElement* node = aArray[i - 1];
372
373 // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
374 // node is in fact a descendant of the form and hence should stay in the
375 // form. If it _is_ set, then we need to check whether the node is a
376 // descendant of aRemovalRoot. If it is, we leave it in the form.
377 #ifdef DEBUG
378 bool removed = false;
379 #endif
380 if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
381 node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
382 if (!node->IsInclusiveDescendantOf(aRemovalRoot)) {
383 nsCOMPtr<nsIFormControl> fc = do_QueryInterface(node);
384 MOZ_ASSERT(fc);
385 fc->ClearForm(true, false);
386
387 // When a form control loses its form owner, its state can change.
388 node->UpdateState(true);
389 #ifdef DEBUG
390 removed = true;
391 #endif
392 }
393 }
394
395 #ifdef DEBUG
396 if (!removed) {
397 nsCOMPtr<nsIFormControl> fc = do_QueryInterface(node);
398 MOZ_ASSERT(fc);
399 HTMLFormElement* form = fc->GetForm();
400 NS_ASSERTION(form == aThisForm, "How did that happen?");
401 }
402 #endif /* DEBUG */
403 }
404 }
405
CollectOrphans(nsINode * aRemovalRoot,const nsTArray<HTMLImageElement * > & aArray,HTMLFormElement * aThisForm)406 static void CollectOrphans(nsINode* aRemovalRoot,
407 const nsTArray<HTMLImageElement*>& aArray
408 #ifdef DEBUG
409 ,
410 HTMLFormElement* aThisForm
411 #endif
412 ) {
413 // Walk backwards so that if we remove elements we can just keep iterating
414 uint32_t length = aArray.Length();
415 for (uint32_t i = length; i > 0; --i) {
416 HTMLImageElement* node = aArray[i - 1];
417
418 // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
419 // node is in fact a descendant of the form and hence should stay in the
420 // form. If it _is_ set, then we need to check whether the node is a
421 // descendant of aRemovalRoot. If it is, we leave it in the form.
422 #ifdef DEBUG
423 bool removed = false;
424 #endif
425 if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
426 node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
427 if (!node->IsInclusiveDescendantOf(aRemovalRoot)) {
428 node->ClearForm(true);
429
430 #ifdef DEBUG
431 removed = true;
432 #endif
433 }
434 }
435
436 #ifdef DEBUG
437 if (!removed) {
438 HTMLFormElement* form = node->GetForm();
439 NS_ASSERTION(form == aThisForm, "How did that happen?");
440 }
441 #endif /* DEBUG */
442 }
443 }
444
UnbindFromTree(bool aNullParent)445 void HTMLFormElement::UnbindFromTree(bool aNullParent) {
446 MaybeFireFormRemoved();
447
448 // Note, this is explicitly using uncomposed doc, since we count
449 // only forms in document.
450 RefPtr<Document> oldDocument = GetUncomposedDoc();
451
452 // Mark all of our controls as maybe being orphans
453 MarkOrphans(mControls->mElements);
454 MarkOrphans(mControls->mNotInElements);
455 MarkOrphans(mImageElements);
456
457 nsGenericHTMLElement::UnbindFromTree(aNullParent);
458
459 nsINode* ancestor = this;
460 nsINode* cur;
461 do {
462 cur = ancestor->GetParentNode();
463 if (!cur) {
464 break;
465 }
466 ancestor = cur;
467 } while (1);
468
469 CollectOrphans(ancestor, mControls->mElements
470 #ifdef DEBUG
471 ,
472 this
473 #endif
474 );
475 CollectOrphans(ancestor, mControls->mNotInElements
476 #ifdef DEBUG
477 ,
478 this
479 #endif
480 );
481 CollectOrphans(ancestor, mImageElements
482 #ifdef DEBUG
483 ,
484 this
485 #endif
486 );
487
488 if (oldDocument && oldDocument->IsHTMLOrXHTML()) {
489 oldDocument->AsHTMLDocument()->RemovedForm();
490 }
491 ForgetCurrentSubmission();
492 }
493
CanSubmit(WidgetEvent & aEvent)494 static bool CanSubmit(WidgetEvent& aEvent) {
495 // According to the UI events spec section "Trusted events", we shouldn't
496 // trigger UA default action with an untrusted event except click.
497 // However, there are still some sites depending on sending untrusted event
498 // to submit form, see Bug 1370630.
499 return !StaticPrefs::dom_forms_submit_trusted_event_only() ||
500 aEvent.IsTrusted();
501 }
502
GetEventTargetParent(EventChainPreVisitor & aVisitor)503 void HTMLFormElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
504 aVisitor.mWantsWillHandleEvent = true;
505 if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
506 CanSubmit(*aVisitor.mEvent)) {
507 uint32_t msg = aVisitor.mEvent->mMessage;
508 if (msg == eFormSubmit) {
509 if (mGeneratingSubmit) {
510 aVisitor.mCanHandle = false;
511 return;
512 }
513 mGeneratingSubmit = true;
514
515 // let the form know that it needs to defer the submission,
516 // that means that if there are scripted submissions, the
517 // latest one will be deferred until after the exit point of the handler.
518 mDeferSubmission = true;
519 } else if (msg == eFormReset) {
520 if (mGeneratingReset) {
521 aVisitor.mCanHandle = false;
522 return;
523 }
524 mGeneratingReset = true;
525 }
526 }
527 nsGenericHTMLElement::GetEventTargetParent(aVisitor);
528 }
529
WillHandleEvent(EventChainPostVisitor & aVisitor)530 void HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor) {
531 // If this is the bubble stage and there is a nested form below us which
532 // received a submit event we do *not* want to handle the submit event
533 // for this form too.
534 if ((aVisitor.mEvent->mMessage == eFormSubmit ||
535 aVisitor.mEvent->mMessage == eFormReset) &&
536 aVisitor.mEvent->mFlags.mInBubblingPhase &&
537 aVisitor.mEvent->mOriginalTarget != static_cast<nsIContent*>(this)) {
538 aVisitor.mEvent->StopPropagation();
539 }
540 }
541
PostHandleEvent(EventChainPostVisitor & aVisitor)542 nsresult HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
543 if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
544 CanSubmit(*aVisitor.mEvent)) {
545 EventMessage msg = aVisitor.mEvent->mMessage;
546 if (msg == eFormSubmit) {
547 // let the form know not to defer subsequent submissions
548 mDeferSubmission = false;
549 }
550
551 if (aVisitor.mEventStatus == nsEventStatus_eIgnore) {
552 switch (msg) {
553 case eFormReset: {
554 DoReset();
555 break;
556 }
557 case eFormSubmit: {
558 if (mPendingSubmission) {
559 // tell the form to forget a possible pending submission.
560 // the reason is that the script returned true (the event was
561 // ignored) so if there is a stored submission, it will miss
562 // the name/value of the submitting element, thus we need
563 // to forget it and the form element will build a new one
564 mPendingSubmission = nullptr;
565 }
566 if (!aVisitor.mEvent->IsTrusted()) {
567 // Warning about the form submission is from untrusted event.
568 OwnerDoc()->WarnOnceAbout(
569 DeprecatedOperations::eFormSubmissionUntrustedEvent);
570 }
571 DoSubmit(aVisitor.mDOMEvent);
572 break;
573 }
574 default:
575 break;
576 }
577 } else {
578 if (msg == eFormSubmit) {
579 // tell the form to flush a possible pending submission.
580 // the reason is that the script returned false (the event was
581 // not ignored) so if there is a stored submission, it needs to
582 // be submitted immediatelly.
583 FlushPendingSubmission();
584 }
585 }
586
587 if (msg == eFormSubmit) {
588 mGeneratingSubmit = false;
589 } else if (msg == eFormReset) {
590 mGeneratingReset = false;
591 }
592 }
593 return NS_OK;
594 }
595
DoReset()596 nsresult HTMLFormElement::DoReset() {
597 // Make sure the presentation is up-to-date
598 Document* doc = GetComposedDoc();
599 if (doc) {
600 doc->FlushPendingNotifications(FlushType::ContentAndNotify);
601 }
602
603 mEverTriedInvalidSubmit = false;
604 // JBK walk the elements[] array instead of form frame controls - bug 34297
605 uint32_t numElements = mControls->Length();
606 for (uint32_t elementX = 0; elementX < numElements; ++elementX) {
607 // Hold strong ref in case the reset does something weird
608 nsCOMPtr<nsIFormControl> controlNode = do_QueryInterface(
609 mControls->mElements.SafeElementAt(elementX, nullptr));
610 if (controlNode) {
611 controlNode->Reset();
612 }
613 }
614
615 return NS_OK;
616 }
617
618 #define NS_ENSURE_SUBMIT_SUCCESS(rv) \
619 if (NS_FAILED(rv)) { \
620 ForgetCurrentSubmission(); \
621 return rv; \
622 }
623
DoSubmit(Event * aEvent)624 nsresult HTMLFormElement::DoSubmit(Event* aEvent) {
625 Document* doc = GetComposedDoc();
626 NS_ASSERTION(doc, "Should never get here without a current doc");
627
628 // Make sure the presentation is up-to-date
629 if (doc) {
630 doc->FlushPendingNotifications(FlushType::ContentAndNotify);
631 }
632
633 // Don't submit if we're not in a document or if we're in
634 // a sandboxed frame and form submit is disabled.
635 if (mIsConstructingEntryList || !doc ||
636 (doc->GetSandboxFlags() & SANDBOXED_FORMS)) {
637 return NS_OK;
638 }
639
640 if (IsSubmitting()) {
641 NS_WARNING("Preventing double form submission");
642 // XXX Should this return an error?
643 return NS_OK;
644 }
645
646 mTargetContext = nullptr;
647 mCurrentLoadId = Nothing();
648
649 UniquePtr<HTMLFormSubmission> submission;
650
651 //
652 // prepare the submission object
653 //
654 nsresult rv = BuildSubmission(getter_Transfers(submission), aEvent);
655
656 // Don't raise an error if form cannot navigate.
657 if (rv == NS_ERROR_NOT_AVAILABLE) {
658 return NS_OK;
659 }
660
661 NS_ENSURE_SUCCESS(rv, rv);
662
663 // XXXbz if the script global is that for an sXBL/XBL2 doc, it won't
664 // be a window...
665 nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
666 if (window) {
667 mSubmitPopupState = PopupBlocker::GetPopupControlState();
668 } else {
669 mSubmitPopupState = PopupBlocker::openAbused;
670 }
671
672 //
673 // perform the submission
674 //
675 if (!submission) {
676 #ifdef DEBUG
677 HTMLDialogElement* dialog = nullptr;
678 for (nsIContent* parent = GetParent(); parent;
679 parent = parent->GetParent()) {
680 dialog = HTMLDialogElement::FromNodeOrNull(parent);
681 if (dialog) {
682 break;
683 }
684 }
685 MOZ_ASSERT(!dialog || !dialog->Open());
686 #endif
687 return NS_OK;
688 }
689
690 if (DialogFormSubmission* dialogSubmission =
691 submission->GetAsDialogSubmission()) {
692 return SubmitDialog(dialogSubmission);
693 }
694
695 if (mDeferSubmission) {
696 // we are in an event handler, JS submitted so we have to
697 // defer this submission. let's remember it and return
698 // without submitting
699 mPendingSubmission = std::move(submission);
700 return NS_OK;
701 }
702
703 return SubmitSubmission(submission.get());
704 }
705
BuildSubmission(HTMLFormSubmission ** aFormSubmission,Event * aEvent)706 nsresult HTMLFormElement::BuildSubmission(HTMLFormSubmission** aFormSubmission,
707 Event* aEvent) {
708 NS_ASSERTION(!mPendingSubmission, "tried to build two submissions!");
709
710 // Get the submitter element
711 nsGenericHTMLElement* submitter = nullptr;
712 if (aEvent) {
713 SubmitEvent* submitEvent = aEvent->AsSubmitEvent();
714 if (submitEvent) {
715 submitter = submitEvent->GetSubmitter();
716 }
717 }
718
719 nsresult rv;
720
721 //
722 // Walk over the form elements and call SubmitNamesValues() on them to get
723 // their data.
724 //
725 auto encoding = GetSubmitEncoding()->OutputEncoding();
726 RefPtr<FormData> formData =
727 new FormData(GetOwnerGlobal(), encoding, submitter);
728 rv = ConstructEntryList(formData);
729 NS_ENSURE_SUBMIT_SUCCESS(rv);
730
731 // Step 9. If form cannot navigate, then return.
732 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
733 if (!GetComposedDoc()) {
734 return NS_ERROR_NOT_AVAILABLE;
735 }
736
737 //
738 // Get the submission object
739 //
740 rv = HTMLFormSubmission::GetFromForm(this, submitter, encoding,
741 aFormSubmission);
742 NS_ENSURE_SUBMIT_SUCCESS(rv);
743
744 //
745 // Dump the data into the submission object
746 //
747 if (!(*aFormSubmission)->GetAsDialogSubmission()) {
748 rv = formData->CopySubmissionDataTo(*aFormSubmission);
749 NS_ENSURE_SUBMIT_SUCCESS(rv);
750 }
751
752 return NS_OK;
753 }
754
SubmitSubmission(HTMLFormSubmission * aFormSubmission)755 nsresult HTMLFormElement::SubmitSubmission(
756 HTMLFormSubmission* aFormSubmission) {
757 nsresult rv;
758
759 nsCOMPtr<nsIURI> actionURI = aFormSubmission->GetActionURL();
760 if (!actionURI) {
761 return NS_OK;
762 }
763
764 // If there is no link handler, then we won't actually be able to submit.
765 Document* doc = GetComposedDoc();
766 nsCOMPtr<nsIDocShell> container = doc ? doc->GetDocShell() : nullptr;
767 if (!container || IsEditable()) {
768 return NS_OK;
769 }
770
771 // javascript URIs are not really submissions; they just call a function.
772 // Also, they may synchronously call submit(), and we want them to be able to
773 // do so while still disallowing other double submissions. (Bug 139798)
774 // Note that any other URI types that are of equivalent type should also be
775 // added here.
776 // XXXbz this is a mess. The real issue here is that nsJSChannel sets the
777 // LOAD_BACKGROUND flag, so doesn't notify us, compounded by the fact that
778 // the JS executes before we forget the submission in OnStateChange on
779 // STATE_STOP. As a result, we have to make sure that we simply pretend
780 // we're not submitting when submitting to a JS URL. That's kinda bogus, but
781 // there we are.
782 bool schemeIsJavaScript = actionURI->SchemeIs("javascript");
783
784 //
785 // Notify observers of submit
786 //
787 bool cancelSubmit = false;
788 if (mNotifiedObservers) {
789 cancelSubmit = mNotifiedObserversResult;
790 } else {
791 rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
792 NS_ENSURE_SUBMIT_SUCCESS(rv);
793 }
794
795 if (cancelSubmit) {
796 return NS_OK;
797 }
798
799 cancelSubmit = false;
800 rv = NotifySubmitObservers(actionURI, &cancelSubmit, false);
801 NS_ENSURE_SUBMIT_SUCCESS(rv);
802
803 if (cancelSubmit) {
804 return NS_OK;
805 }
806
807 //
808 // Submit
809 //
810 nsCOMPtr<nsIDocShell> docShell;
811 uint64_t currentLoadId = 0;
812
813 {
814 AutoPopupStatePusher popupStatePusher(mSubmitPopupState);
815
816 AutoHandlingUserInputStatePusher userInpStatePusher(
817 aFormSubmission->IsInitiatedFromUserInput());
818
819 nsCOMPtr<nsIInputStream> postDataStream;
820 rv = aFormSubmission->GetEncodedSubmission(
821 actionURI, getter_AddRefs(postDataStream), actionURI);
822 NS_ENSURE_SUBMIT_SUCCESS(rv);
823
824 nsAutoString target;
825 aFormSubmission->GetTarget(target);
826
827 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(actionURI);
828 loadState->SetTarget(target);
829 loadState->SetPostDataStream(postDataStream);
830 loadState->SetFirstParty(true);
831 loadState->SetIsFormSubmission(true);
832 loadState->SetTriggeringPrincipal(NodePrincipal());
833 loadState->SetPrincipalToInherit(NodePrincipal());
834 loadState->SetCsp(GetCsp());
835 loadState->SetAllowFocusMove(UserActivation::IsHandlingUserInput());
836
837 rv = nsDocShell::Cast(container)->OnLinkClickSync(this, loadState, false,
838 NodePrincipal());
839 NS_ENSURE_SUBMIT_SUCCESS(rv);
840
841 mTargetContext = loadState->TargetBrowsingContext().GetMaybeDiscarded();
842 currentLoadId = loadState->GetLoadIdentifier();
843 }
844
845 // Even if the submit succeeds, it's possible for there to be no
846 // browsing context; for example, if it's to a named anchor within
847 // the same page the submit will not really do anything.
848 if (mTargetContext && !mTargetContext->IsDiscarded() && !schemeIsJavaScript) {
849 mCurrentLoadId = Some(currentLoadId);
850 } else {
851 ForgetCurrentSubmission();
852 }
853
854 return rv;
855 }
856
857 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-dialog
SubmitDialog(DialogFormSubmission * aFormSubmission)858 nsresult HTMLFormElement::SubmitDialog(DialogFormSubmission* aFormSubmission) {
859 // Close the dialog subject. If there is a result, let that be the return
860 // value.
861 HTMLDialogElement* dialog = aFormSubmission->DialogElement();
862 MOZ_ASSERT(dialog);
863
864 Optional<nsAString> retValue;
865 retValue = &aFormSubmission->ReturnValue();
866 dialog->Close(retValue);
867
868 return NS_OK;
869 }
870
DoSecureToInsecureSubmitCheck(nsIURI * aActionURL,bool * aCancelSubmit)871 nsresult HTMLFormElement::DoSecureToInsecureSubmitCheck(nsIURI* aActionURL,
872 bool* aCancelSubmit) {
873 *aCancelSubmit = false;
874
875 if (!StaticPrefs::security_warn_submit_secure_to_insecure()) {
876 return NS_OK;
877 }
878
879 // Only ask the user about posting from a secure URI to an insecure URI if
880 // this element is in the root document. When this is not the case, the mixed
881 // content blocker will take care of security for us.
882 if (!OwnerDoc()->IsTopLevelContentDocument()) {
883 return NS_OK;
884 }
885
886 nsIPrincipal* principal = NodePrincipal();
887 if (!principal) {
888 *aCancelSubmit = true;
889 return NS_OK;
890 }
891 bool formIsHTTPS = principal->SchemeIs("https");
892 if (principal->IsSystemPrincipal() || principal->GetIsExpandedPrincipal()) {
893 formIsHTTPS = OwnerDoc()->GetDocumentURI()->SchemeIs("https");
894 }
895 if (!formIsHTTPS) {
896 return NS_OK;
897 }
898
899 if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aActionURL)) {
900 return NS_OK;
901 }
902
903 if (nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(aActionURL)) {
904 return NS_OK;
905 }
906
907 if (nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(aActionURL)) {
908 return NS_OK;
909 }
910
911 nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
912 if (!window) {
913 return NS_ERROR_FAILURE;
914 }
915 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
916 if (!docShell) {
917 return NS_ERROR_FAILURE;
918 }
919
920 nsresult rv;
921 nsCOMPtr<nsIPromptService> promptSvc =
922 do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv);
923 if (NS_FAILED(rv)) {
924 return rv;
925 }
926
927 nsCOMPtr<nsIStringBundle> stringBundle;
928 nsCOMPtr<nsIStringBundleService> stringBundleService =
929 mozilla::components::StringBundle::Service();
930 if (!stringBundleService) {
931 return NS_ERROR_FAILURE;
932 }
933 rv = stringBundleService->CreateBundle(
934 "chrome://global/locale/browser.properties",
935 getter_AddRefs(stringBundle));
936 if (NS_FAILED(rv)) {
937 return rv;
938 }
939 nsAutoString title;
940 nsAutoString message;
941 nsAutoString cont;
942 stringBundle->GetStringFromName("formPostSecureToInsecureWarning.title",
943 title);
944 stringBundle->GetStringFromName("formPostSecureToInsecureWarning.message",
945 message);
946 stringBundle->GetStringFromName("formPostSecureToInsecureWarning.continue",
947 cont);
948 int32_t buttonPressed;
949 bool checkState =
950 false; // this is unused (ConfirmEx requires this parameter)
951 rv = promptSvc->ConfirmExBC(
952 docShell->GetBrowsingContext(),
953 StaticPrefs::prompts_modalType_insecureFormSubmit(), title.get(),
954 message.get(),
955 (nsIPromptService::BUTTON_TITLE_IS_STRING *
956 nsIPromptService::BUTTON_POS_0) +
957 (nsIPromptService::BUTTON_TITLE_CANCEL *
958 nsIPromptService::BUTTON_POS_1),
959 cont.get(), nullptr, nullptr, nullptr, &checkState, &buttonPressed);
960 if (NS_FAILED(rv)) {
961 return rv;
962 }
963 *aCancelSubmit = (buttonPressed == 1);
964 uint32_t telemetryBucket =
965 nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE;
966 mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI,
967 telemetryBucket);
968 if (!*aCancelSubmit) {
969 // The user opted to continue, so note that in the next telemetry bucket.
970 mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI,
971 telemetryBucket + 1);
972 }
973 return NS_OK;
974 }
975
NotifySubmitObservers(nsIURI * aActionURL,bool * aCancelSubmit,bool aEarlyNotify)976 nsresult HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL,
977 bool* aCancelSubmit,
978 bool aEarlyNotify) {
979 if (!aEarlyNotify) {
980 nsresult rv = DoSecureToInsecureSubmitCheck(aActionURL, aCancelSubmit);
981 if (NS_FAILED(rv)) {
982 return rv;
983 }
984 if (*aCancelSubmit) {
985 return NS_OK;
986 }
987 }
988
989 bool defaultAction = true;
990 nsresult rv = nsContentUtils::DispatchEventOnlyToChrome(
991 OwnerDoc(), static_cast<nsINode*>(this),
992 aEarlyNotify ? u"DOMFormBeforeSubmit"_ns : u"DOMFormSubmit"_ns,
993 CanBubble::eYes, Cancelable::eYes, &defaultAction);
994 *aCancelSubmit = !defaultAction;
995 if (*aCancelSubmit) {
996 return NS_OK;
997 }
998 return rv;
999 }
1000
ConstructEntryList(FormData * aFormData)1001 nsresult HTMLFormElement::ConstructEntryList(FormData* aFormData) {
1002 MOZ_ASSERT(aFormData, "Must have FormData!");
1003 if (mIsConstructingEntryList) {
1004 // Step 2.2 of https://xhr.spec.whatwg.org/#dom-formdata.
1005 return NS_ERROR_DOM_INVALID_STATE_ERR;
1006 }
1007
1008 AutoRestore<bool> resetConstructingEntryList(mIsConstructingEntryList);
1009 mIsConstructingEntryList = true;
1010 // This shouldn't be called recursively, so use a rather large value
1011 // for the preallocated buffer.
1012 AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls;
1013 nsresult rv = mControls->GetSortedControls(sortedControls);
1014 NS_ENSURE_SUCCESS(rv, rv);
1015
1016 uint32_t len = sortedControls.Length();
1017
1018 //
1019 // Walk the list of nodes and call SubmitNamesValues() on the controls
1020 //
1021 for (uint32_t i = 0; i < len; ++i) {
1022 // Disabled elements don't submit
1023 if (!sortedControls[i]->IsDisabled()) {
1024 nsCOMPtr<nsIFormControl> fc = do_QueryInterface(sortedControls[i]);
1025 MOZ_ASSERT(fc);
1026 // Tell the control to submit its name/value pairs to the submission
1027 fc->SubmitNamesValues(aFormData);
1028 }
1029 }
1030
1031 FormDataEventInit init;
1032 init.mBubbles = true;
1033 init.mCancelable = false;
1034 init.mFormData = aFormData;
1035 RefPtr<FormDataEvent> event =
1036 FormDataEvent::Constructor(this, u"formdata"_ns, init);
1037 event->SetTrusted(true);
1038
1039 // TODO: Bug 1506441
1040 EventDispatcher::DispatchDOMEvent(MOZ_KnownLive(ToSupports(this)), nullptr,
1041 event, nullptr, nullptr);
1042
1043 return NS_OK;
1044 }
1045
GetSubmitEncoding()1046 NotNull<const Encoding*> HTMLFormElement::GetSubmitEncoding() {
1047 nsAutoString acceptCharsetValue;
1048 GetAttr(kNameSpaceID_None, nsGkAtoms::acceptcharset, acceptCharsetValue);
1049
1050 int32_t charsetLen = acceptCharsetValue.Length();
1051 if (charsetLen > 0) {
1052 int32_t offset = 0;
1053 int32_t spPos = 0;
1054 // get charset from charsets one by one
1055 do {
1056 spPos = acceptCharsetValue.FindChar(char16_t(' '), offset);
1057 int32_t cnt = ((-1 == spPos) ? (charsetLen - offset) : (spPos - offset));
1058 if (cnt > 0) {
1059 nsAutoString uCharset;
1060 acceptCharsetValue.Mid(uCharset, offset, cnt);
1061
1062 auto encoding = Encoding::ForLabelNoReplacement(uCharset);
1063 if (encoding) {
1064 return WrapNotNull(encoding);
1065 }
1066 }
1067 offset = spPos + 1;
1068 } while (spPos != -1);
1069 }
1070 // if there are no accept-charset or all the charset are not supported
1071 // Get the charset from document
1072 Document* doc = GetComposedDoc();
1073 if (doc) {
1074 return doc->GetDocumentCharacterSet();
1075 }
1076 return UTF_8_ENCODING;
1077 }
1078
IndexedGetter(uint32_t aIndex,bool & aFound)1079 Element* HTMLFormElement::IndexedGetter(uint32_t aIndex, bool& aFound) {
1080 Element* element = mControls->mElements.SafeElementAt(aIndex, nullptr);
1081 aFound = element != nullptr;
1082 return element;
1083 }
1084
1085 /**
1086 * Compares the position of aControl1 and aControl2 in the document
1087 * @param aControl1 First control to compare.
1088 * @param aControl2 Second control to compare.
1089 * @param aForm Parent form of the controls.
1090 * @return < 0 if aControl1 is before aControl2,
1091 * > 0 if aControl1 is after aControl2,
1092 * 0 otherwise
1093 */
1094 /* static */
CompareFormControlPosition(Element * aElement1,Element * aElement2,const nsIContent * aForm)1095 int32_t HTMLFormElement::CompareFormControlPosition(Element* aElement1,
1096 Element* aElement2,
1097 const nsIContent* aForm) {
1098 NS_ASSERTION(aElement1 != aElement2, "Comparing a form control to itself");
1099
1100 // If an element has a @form, we can assume it *might* be able to not have
1101 // a parent and still be in the form.
1102 NS_ASSERTION((aElement1->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1103 aElement1->GetParent()) &&
1104 (aElement2->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1105 aElement2->GetParent()),
1106 "Form controls should always have parents");
1107
1108 // If we pass aForm, we are assuming both controls are form descendants which
1109 // is not always the case. This function should work but maybe slower.
1110 // However, checking if both elements are form descendants may be slow too...
1111 // TODO: remove the prevent asserts fix, see bug 598468.
1112 #ifdef DEBUG
1113 nsLayoutUtils::gPreventAssertInCompareTreePosition = true;
1114 int32_t rVal =
1115 nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
1116 nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
1117
1118 return rVal;
1119 #else // DEBUG
1120 return nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
1121 #endif // DEBUG
1122 }
1123
1124 #ifdef DEBUG
1125 /**
1126 * Checks that all form elements are in document order. Asserts if any pair of
1127 * consecutive elements are not in increasing document order.
1128 *
1129 * @param aControls List of form controls to check.
1130 * @param aForm Parent form of the controls.
1131 */
1132 /* static */
AssertDocumentOrder(const nsTArray<nsGenericHTMLFormElement * > & aControls,nsIContent * aForm)1133 void HTMLFormElement::AssertDocumentOrder(
1134 const nsTArray<nsGenericHTMLFormElement*>& aControls, nsIContent* aForm) {
1135 // TODO: remove the if directive with bug 598468.
1136 // This is done to prevent asserts in some edge cases.
1137 # if 0
1138 // Only iterate if aControls is not empty, since otherwise
1139 // |aControls.Length() - 1| will be a very large unsigned number... not what
1140 // we want here.
1141 if (!aControls.IsEmpty()) {
1142 for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
1143 NS_ASSERTION(
1144 CompareFormControlPosition(aControls[i], aControls[i + 1], aForm) < 0,
1145 "Form controls not ordered correctly");
1146 }
1147 }
1148 # endif
1149 }
1150
1151 /**
1152 * Copy of the above function, but with RefPtrs.
1153 *
1154 * @param aControls List of form controls to check.
1155 * @param aForm Parent form of the controls.
1156 */
1157 /* static */
AssertDocumentOrder(const nsTArray<RefPtr<nsGenericHTMLFormElement>> & aControls,nsIContent * aForm)1158 void HTMLFormElement::AssertDocumentOrder(
1159 const nsTArray<RefPtr<nsGenericHTMLFormElement>>& aControls,
1160 nsIContent* aForm) {
1161 // TODO: remove the if directive with bug 598468.
1162 // This is done to prevent asserts in some edge cases.
1163 # if 0
1164 // Only iterate if aControls is not empty, since otherwise
1165 // |aControls.Length() - 1| will be a very large unsigned number... not what
1166 // we want here.
1167 if (!aControls.IsEmpty()) {
1168 for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
1169 NS_ASSERTION(
1170 CompareFormControlPosition(aControls[i], aControls[i + 1], aForm) < 0,
1171 "Form controls not ordered correctly");
1172 }
1173 }
1174 # endif
1175 }
1176 #endif
1177
PostPasswordEvent()1178 void HTMLFormElement::PostPasswordEvent() {
1179 // Don't fire another add event if we have a pending add event.
1180 if (mFormPasswordEventDispatcher.get()) {
1181 return;
1182 }
1183
1184 mFormPasswordEventDispatcher =
1185 new AsyncEventDispatcher(this, u"DOMFormHasPassword"_ns, CanBubble::eYes,
1186 ChromeOnlyDispatch::eYes);
1187 mFormPasswordEventDispatcher->PostDOMEvent();
1188 }
1189
PostPossibleUsernameEvent()1190 void HTMLFormElement::PostPossibleUsernameEvent() {
1191 if (!StaticPrefs::signon_usernameOnlyForm_enabled()) {
1192 return;
1193 }
1194
1195 // Don't fire another event if we have a pending event.
1196 if (mFormPossibleUsernameEventDispatcher) {
1197 return;
1198 }
1199
1200 mFormPossibleUsernameEventDispatcher =
1201 new AsyncEventDispatcher(this, u"DOMFormHasPossibleUsername"_ns,
1202 CanBubble::eYes, ChromeOnlyDispatch::eYes);
1203 mFormPossibleUsernameEventDispatcher->PostDOMEvent();
1204 }
1205
1206 namespace {
1207
1208 struct FormComparator {
1209 Element* const mChild;
1210 HTMLFormElement* const mForm;
FormComparatormozilla::dom::__anon0cfbb8960111::FormComparator1211 FormComparator(Element* aChild, HTMLFormElement* aForm)
1212 : mChild(aChild), mForm(aForm) {}
operator ()mozilla::dom::__anon0cfbb8960111::FormComparator1213 int operator()(Element* aElement) const {
1214 return HTMLFormElement::CompareFormControlPosition(mChild, aElement, mForm);
1215 }
1216 };
1217
1218 } // namespace
1219
1220 // This function return true if the element, once appended, is the last one in
1221 // the array.
1222 template <typename ElementType>
AddElementToList(nsTArray<ElementType * > & aList,ElementType * aChild,HTMLFormElement * aForm)1223 static bool AddElementToList(nsTArray<ElementType*>& aList, ElementType* aChild,
1224 HTMLFormElement* aForm) {
1225 NS_ASSERTION(aList.IndexOf(aChild) == aList.NoIndex,
1226 "aChild already in aList");
1227
1228 const uint32_t count = aList.Length();
1229 ElementType* element;
1230 bool lastElement = false;
1231
1232 // Optimize most common case where we insert at the end.
1233 int32_t position = -1;
1234 if (count > 0) {
1235 element = aList[count - 1];
1236 position =
1237 HTMLFormElement::CompareFormControlPosition(aChild, element, aForm);
1238 }
1239
1240 // If this item comes after the last element, or the elements array is
1241 // empty, we append to the end. Otherwise, we do a binary search to
1242 // determine where the element should go.
1243 if (position >= 0 || count == 0) {
1244 // WEAK - don't addref
1245 aList.AppendElement(aChild);
1246 lastElement = true;
1247 } else {
1248 size_t idx;
1249 BinarySearchIf(aList, 0, count, FormComparator(aChild, aForm), &idx);
1250
1251 // WEAK - don't addref
1252 aList.InsertElementAt(idx, aChild);
1253 }
1254
1255 return lastElement;
1256 }
1257
AddElement(nsGenericHTMLFormElement * aChild,bool aUpdateValidity,bool aNotify)1258 nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
1259 bool aUpdateValidity, bool aNotify) {
1260 // If an element has a @form, we can assume it *might* be able to not have
1261 // a parent and still be in the form.
1262 NS_ASSERTION(aChild->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1263 aChild->GetParent(),
1264 "Form control should have a parent");
1265 nsCOMPtr<nsIFormControl> fc = do_QueryObject(aChild);
1266 MOZ_ASSERT(fc);
1267 // Determine whether to add the new element to the elements or
1268 // the not-in-elements list.
1269 bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(fc);
1270 nsTArray<nsGenericHTMLFormElement*>& controlList =
1271 childInElements ? mControls->mElements : mControls->mNotInElements;
1272
1273 bool lastElement = AddElementToList(controlList, aChild, this);
1274
1275 #ifdef DEBUG
1276 AssertDocumentOrder(controlList, this);
1277 #endif
1278
1279 auto type = fc->ControlType();
1280
1281 // If it is a password control, inform the password manager.
1282 if (type == FormControlType::InputPassword) {
1283 PostPasswordEvent();
1284 // If the type is email or text, it is a username compatible input,
1285 // inform the password manager.
1286 } else if (type == FormControlType::InputEmail ||
1287 type == FormControlType::InputText) {
1288 PostPossibleUsernameEvent();
1289 }
1290
1291 // Default submit element handling
1292 if (fc->IsSubmitControl()) {
1293 // Update mDefaultSubmitElement, mFirstSubmitInElements,
1294 // mFirstSubmitNotInElements.
1295
1296 nsGenericHTMLFormElement** firstSubmitSlot =
1297 childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
1298
1299 // The new child is the new first submit in its list if the firstSubmitSlot
1300 // is currently empty or if the child is before what's currently in the
1301 // slot. Note that if we already have a control in firstSubmitSlot and
1302 // we're appending this element can't possibly replace what's currently in
1303 // the slot. Also note that aChild can't become the mDefaultSubmitElement
1304 // unless it replaces what's in the slot. If it _does_ replace what's in
1305 // the slot, it becomes the default submit if either the default submit is
1306 // what's in the slot or the child is earlier than the default submit.
1307 nsGenericHTMLFormElement* oldDefaultSubmit = mDefaultSubmitElement;
1308 if (!*firstSubmitSlot ||
1309 (!lastElement &&
1310 CompareFormControlPosition(aChild, *firstSubmitSlot, this) < 0)) {
1311 // Update mDefaultSubmitElement if it's currently in a valid state.
1312 // Valid state means either non-null or null because there are in fact
1313 // no submit elements around.
1314 if ((mDefaultSubmitElement ||
1315 (!mFirstSubmitInElements && !mFirstSubmitNotInElements)) &&
1316 (*firstSubmitSlot == mDefaultSubmitElement ||
1317 CompareFormControlPosition(aChild, mDefaultSubmitElement, this) <
1318 0)) {
1319 mDefaultSubmitElement = aChild;
1320 }
1321 *firstSubmitSlot = aChild;
1322 }
1323
1324 MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements ||
1325 mDefaultSubmitElement == mFirstSubmitNotInElements ||
1326 !mDefaultSubmitElement,
1327 "What happened here?");
1328
1329 // Notify that the state of the previous default submit element has changed
1330 // if the element which is the default submit element has changed. The new
1331 // default submit element is responsible for its own state update.
1332 if (oldDefaultSubmit && oldDefaultSubmit != mDefaultSubmitElement) {
1333 oldDefaultSubmit->UpdateState(aNotify);
1334 }
1335 }
1336
1337 // If the element is subject to constraint validaton and is invalid, we need
1338 // to update our internal counter.
1339 if (aUpdateValidity) {
1340 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
1341 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
1342 !cvElmt->IsValid()) {
1343 UpdateValidity(false);
1344 }
1345 }
1346
1347 // Notify the radio button it's been added to a group
1348 // This has to be done _after_ UpdateValidity() call to prevent the element
1349 // being count twice.
1350 if (type == FormControlType::InputRadio) {
1351 RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
1352 radio->AddedToRadioGroup();
1353 }
1354
1355 return NS_OK;
1356 }
1357
AddElementToTable(nsGenericHTMLFormElement * aChild,const nsAString & aName)1358 nsresult HTMLFormElement::AddElementToTable(nsGenericHTMLFormElement* aChild,
1359 const nsAString& aName) {
1360 return mControls->AddElementToTable(aChild, aName);
1361 }
1362
RemoveElement(nsGenericHTMLFormElement * aChild,bool aUpdateValidity)1363 nsresult HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
1364 bool aUpdateValidity) {
1365 RemoveElementFromPastNamesMap(aChild);
1366
1367 //
1368 // Remove it from the radio group if it's a radio button
1369 //
1370 nsresult rv = NS_OK;
1371 nsCOMPtr<nsIFormControl> fc = do_QueryInterface(aChild);
1372 MOZ_ASSERT(fc);
1373 if (fc->ControlType() == FormControlType::InputRadio) {
1374 RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
1375 radio->WillRemoveFromRadioGroup();
1376 }
1377
1378 // Determine whether to remove the child from the elements list
1379 // or the not in elements list.
1380 bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(fc);
1381 nsTArray<nsGenericHTMLFormElement*>& controls =
1382 childInElements ? mControls->mElements : mControls->mNotInElements;
1383
1384 // Find the index of the child. This will be used later if necessary
1385 // to find the default submit.
1386 size_t index = controls.IndexOf(aChild);
1387 NS_ENSURE_STATE(index != controls.NoIndex);
1388
1389 controls.RemoveElementAt(index);
1390
1391 // Update our mFirstSubmit* values.
1392 nsGenericHTMLFormElement** firstSubmitSlot =
1393 childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
1394 if (aChild == *firstSubmitSlot) {
1395 *firstSubmitSlot = nullptr;
1396
1397 // We are removing the first submit in this list, find the new first submit
1398 uint32_t length = controls.Length();
1399 for (uint32_t i = index; i < length; ++i) {
1400 nsCOMPtr<nsIFormControl> currentControl = do_QueryInterface(controls[i]);
1401 MOZ_ASSERT(currentControl);
1402 if (currentControl->IsSubmitControl()) {
1403 *firstSubmitSlot = controls[i];
1404 break;
1405 }
1406 }
1407 }
1408
1409 if (aChild == mDefaultSubmitElement) {
1410 // Need to reset mDefaultSubmitElement. Do this asynchronously so
1411 // that we're not doing it while the DOM is in flux.
1412 mDefaultSubmitElement = nullptr;
1413 nsContentUtils::AddScriptRunner(new RemoveElementRunnable(this));
1414
1415 // Note that we don't need to notify on the old default submit (which is
1416 // being removed) because it's either being removed from the DOM or
1417 // changing attributes in a way that makes it responsible for sending its
1418 // own notifications.
1419 }
1420
1421 // If the element was subject to constraint validaton and is invalid, we need
1422 // to update our internal counter.
1423 if (aUpdateValidity) {
1424 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
1425 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
1426 !cvElmt->IsValid()) {
1427 UpdateValidity(true);
1428 }
1429 }
1430
1431 return rv;
1432 }
1433
HandleDefaultSubmitRemoval()1434 void HTMLFormElement::HandleDefaultSubmitRemoval() {
1435 if (mDefaultSubmitElement) {
1436 // Already got reset somehow; nothing else to do here
1437 return;
1438 }
1439
1440 if (!mFirstSubmitNotInElements) {
1441 mDefaultSubmitElement = mFirstSubmitInElements;
1442 } else if (!mFirstSubmitInElements) {
1443 mDefaultSubmitElement = mFirstSubmitNotInElements;
1444 } else {
1445 NS_ASSERTION(mFirstSubmitInElements != mFirstSubmitNotInElements,
1446 "How did that happen?");
1447 // Have both; use the earlier one
1448 mDefaultSubmitElement =
1449 CompareFormControlPosition(mFirstSubmitInElements,
1450 mFirstSubmitNotInElements, this) < 0
1451 ? mFirstSubmitInElements
1452 : mFirstSubmitNotInElements;
1453 }
1454
1455 MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements ||
1456 mDefaultSubmitElement == mFirstSubmitNotInElements,
1457 "What happened here?");
1458
1459 // Notify about change if needed.
1460 if (mDefaultSubmitElement) {
1461 mDefaultSubmitElement->UpdateState(true);
1462 }
1463 }
1464
RemoveElementFromTableInternal(nsInterfaceHashtable<nsStringHashKey,nsISupports> & aTable,nsIContent * aChild,const nsAString & aName)1465 nsresult HTMLFormElement::RemoveElementFromTableInternal(
1466 nsInterfaceHashtable<nsStringHashKey, nsISupports>& aTable,
1467 nsIContent* aChild, const nsAString& aName) {
1468 auto entry = aTable.Lookup(aName);
1469 if (!entry) {
1470 return NS_OK;
1471 }
1472 // Single element in the hash, just remove it if it's the one
1473 // we're trying to remove...
1474 if (entry.Data() == aChild) {
1475 entry.Remove();
1476 ++mExpandoAndGeneration.generation;
1477 return NS_OK;
1478 }
1479
1480 nsCOMPtr<nsIContent> content(do_QueryInterface(entry.Data()));
1481 if (content) {
1482 return NS_OK;
1483 }
1484
1485 // If it's not a content node then it must be a RadioNodeList.
1486 MOZ_ASSERT(nsCOMPtr<RadioNodeList>(do_QueryInterface(entry.Data())));
1487 auto* list = static_cast<RadioNodeList*>(entry->get());
1488
1489 list->RemoveElement(aChild);
1490
1491 uint32_t length = list->Length();
1492
1493 if (!length) {
1494 // If the list is empty we remove if from our hash, this shouldn't
1495 // happen tho
1496 entry.Remove();
1497 ++mExpandoAndGeneration.generation;
1498 } else if (length == 1) {
1499 // Only one element left, replace the list in the hash with the
1500 // single element.
1501 nsIContent* node = list->Item(0);
1502 if (node) {
1503 entry.Data() = node;
1504 }
1505 }
1506
1507 return NS_OK;
1508 }
1509
RemoveElementFromTable(nsGenericHTMLFormElement * aElement,const nsAString & aName)1510 nsresult HTMLFormElement::RemoveElementFromTable(
1511 nsGenericHTMLFormElement* aElement, const nsAString& aName) {
1512 return mControls->RemoveElementFromTable(aElement, aName);
1513 }
1514
NamedGetter(const nsAString & aName,bool & aFound)1515 already_AddRefed<nsISupports> HTMLFormElement::NamedGetter(
1516 const nsAString& aName, bool& aFound) {
1517 aFound = true;
1518
1519 nsCOMPtr<nsISupports> result = DoResolveName(aName);
1520 if (result) {
1521 AddToPastNamesMap(aName, result);
1522 return result.forget();
1523 }
1524
1525 result = mImageNameLookupTable.GetWeak(aName);
1526 if (result) {
1527 AddToPastNamesMap(aName, result);
1528 return result.forget();
1529 }
1530
1531 result = mPastNameLookupTable.GetWeak(aName);
1532 if (result) {
1533 return result.forget();
1534 }
1535
1536 aFound = false;
1537 return nullptr;
1538 }
1539
GetSupportedNames(nsTArray<nsString> & aRetval)1540 void HTMLFormElement::GetSupportedNames(nsTArray<nsString>& aRetval) {
1541 // TODO https://github.com/whatwg/html/issues/1731
1542 }
1543
FindNamedItem(const nsAString & aName,nsWrapperCache ** aCache)1544 already_AddRefed<nsISupports> HTMLFormElement::FindNamedItem(
1545 const nsAString& aName, nsWrapperCache** aCache) {
1546 // FIXME Get the wrapper cache from DoResolveName.
1547
1548 bool found;
1549 nsCOMPtr<nsISupports> result = NamedGetter(aName, found);
1550 if (result) {
1551 *aCache = nullptr;
1552 return result.forget();
1553 }
1554
1555 return nullptr;
1556 }
1557
DoResolveName(const nsAString & aName)1558 already_AddRefed<nsISupports> HTMLFormElement::DoResolveName(
1559 const nsAString& aName) {
1560 nsCOMPtr<nsISupports> result = mControls->NamedItemInternal(aName);
1561 return result.forget();
1562 }
1563
OnSubmitClickBegin(Element * aOriginatingElement)1564 void HTMLFormElement::OnSubmitClickBegin(Element* aOriginatingElement) {
1565 mDeferSubmission = true;
1566
1567 // Prepare to run NotifySubmitObservers early before the
1568 // scripts on the page get to modify the form data, possibly
1569 // throwing off any password manager. (bug 257781)
1570 nsCOMPtr<nsIURI> actionURI;
1571 nsresult rv;
1572
1573 rv = GetActionURL(getter_AddRefs(actionURI), aOriginatingElement);
1574 if (NS_FAILED(rv) || !actionURI) return;
1575
1576 // Notify observers of submit if the form is valid.
1577 // TODO: checking for mInvalidElementsCount is a temporary fix that should be
1578 // removed with bug 610402.
1579 if (mInvalidElementsCount == 0) {
1580 bool cancelSubmit = false;
1581 rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
1582 if (NS_SUCCEEDED(rv)) {
1583 mNotifiedObservers = true;
1584 mNotifiedObserversResult = cancelSubmit;
1585 }
1586 }
1587 }
1588
OnSubmitClickEnd()1589 void HTMLFormElement::OnSubmitClickEnd() { mDeferSubmission = false; }
1590
FlushPendingSubmission()1591 void HTMLFormElement::FlushPendingSubmission() {
1592 if (mPendingSubmission) {
1593 // Transfer owning reference so that the submission doesn't get deleted
1594 // if we reenter
1595 UniquePtr<HTMLFormSubmission> submission = std::move(mPendingSubmission);
1596
1597 SubmitSubmission(submission.get());
1598 }
1599 }
1600
GetAction(nsString & aValue)1601 void HTMLFormElement::GetAction(nsString& aValue) {
1602 if (!GetAttr(kNameSpaceID_None, nsGkAtoms::action, aValue) ||
1603 aValue.IsEmpty()) {
1604 Document* document = OwnerDoc();
1605 nsIURI* docURI = document->GetDocumentURI();
1606 if (docURI) {
1607 nsAutoCString spec;
1608 nsresult rv = docURI->GetSpec(spec);
1609 if (NS_FAILED(rv)) {
1610 return;
1611 }
1612
1613 CopyUTF8toUTF16(spec, aValue);
1614 }
1615 } else {
1616 GetURIAttr(nsGkAtoms::action, nullptr, aValue);
1617 }
1618 }
1619
GetActionURL(nsIURI ** aActionURL,Element * aOriginatingElement)1620 nsresult HTMLFormElement::GetActionURL(nsIURI** aActionURL,
1621 Element* aOriginatingElement) {
1622 nsresult rv = NS_OK;
1623
1624 *aActionURL = nullptr;
1625
1626 //
1627 // Grab the URL string
1628 //
1629 // If the originating element is a submit control and has the formaction
1630 // attribute specified, it should be used. Otherwise, the action attribute
1631 // from the form element should be used.
1632 //
1633 nsAutoString action;
1634
1635 if (aOriginatingElement &&
1636 aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formaction)) {
1637 #ifdef DEBUG
1638 nsCOMPtr<nsIFormControl> formControl =
1639 do_QueryInterface(aOriginatingElement);
1640 NS_ASSERTION(formControl && formControl->IsSubmitControl(),
1641 "The originating element must be a submit form control!");
1642 #endif // DEBUG
1643
1644 HTMLInputElement* inputElement =
1645 HTMLInputElement::FromNode(aOriginatingElement);
1646 if (inputElement) {
1647 inputElement->GetFormAction(action);
1648 } else {
1649 auto buttonElement = HTMLButtonElement::FromNode(aOriginatingElement);
1650 if (buttonElement) {
1651 buttonElement->GetFormAction(action);
1652 } else {
1653 NS_ERROR("Originating element must be an input or button element!");
1654 return NS_ERROR_UNEXPECTED;
1655 }
1656 }
1657 } else {
1658 GetAction(action);
1659 }
1660
1661 //
1662 // Form the full action URL
1663 //
1664
1665 // Get the document to form the URL.
1666 // We'll also need it later to get the DOM window when notifying form submit
1667 // observers (bug 33203)
1668 if (!IsInComposedDoc()) {
1669 return NS_OK; // No doc means don't submit, see Bug 28988
1670 }
1671
1672 // Get base URL
1673 Document* document = OwnerDoc();
1674 nsIURI* docURI = document->GetDocumentURI();
1675 NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
1676
1677 // If an action is not specified and we are inside
1678 // a HTML document then reload the URL. This makes us
1679 // compatible with 4.x browsers.
1680 // If we are in some other type of document such as XML or
1681 // XUL, do nothing. This prevents undesirable reloading of
1682 // a document inside XUL.
1683
1684 nsCOMPtr<nsIURI> actionURL;
1685 if (action.IsEmpty()) {
1686 if (!document->IsHTMLOrXHTML()) {
1687 // Must be a XML, XUL or other non-HTML document type
1688 // so do nothing.
1689 return NS_OK;
1690 }
1691
1692 actionURL = docURI;
1693 } else {
1694 nsIURI* baseURL = GetBaseURI();
1695 NS_ASSERTION(baseURL, "No Base URL found in Form Submit!\n");
1696 if (!baseURL) {
1697 return NS_OK; // No base URL -> exit early, see Bug 30721
1698 }
1699 rv = NS_NewURI(getter_AddRefs(actionURL), action, nullptr, baseURL);
1700 NS_ENSURE_SUCCESS(rv, rv);
1701 }
1702
1703 //
1704 // Verify the URL should be reached
1705 //
1706 // Get security manager, check to see if access to action URI is allowed.
1707 //
1708 nsIScriptSecurityManager* securityManager =
1709 nsContentUtils::GetSecurityManager();
1710 rv = securityManager->CheckLoadURIWithPrincipal(
1711 NodePrincipal(), actionURL, nsIScriptSecurityManager::STANDARD,
1712 OwnerDoc()->InnerWindowID());
1713 NS_ENSURE_SUCCESS(rv, rv);
1714
1715 // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. In
1716 // such a case we have to upgrade the action url from http:// to https://.
1717 // The upgrade is only required if the actionURL is http and not a potentially
1718 // trustworthy loopback URI.
1719 bool needsUpgrade =
1720 actionURL->SchemeIs("http") &&
1721 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(actionURL) &&
1722 document->GetUpgradeInsecureRequests(false);
1723 if (needsUpgrade) {
1724 // let's use the old specification before the upgrade for logging
1725 AutoTArray<nsString, 2> params;
1726 nsAutoCString spec;
1727 rv = actionURL->GetSpec(spec);
1728 NS_ENSURE_SUCCESS(rv, rv);
1729 CopyUTF8toUTF16(spec, *params.AppendElement());
1730
1731 // upgrade the actionURL from http:// to use https://
1732 nsCOMPtr<nsIURI> upgradedActionURL;
1733 rv = NS_GetSecureUpgradedURI(actionURL, getter_AddRefs(upgradedActionURL));
1734 NS_ENSURE_SUCCESS(rv, rv);
1735 actionURL = std::move(upgradedActionURL);
1736
1737 // let's log a message to the console that we are upgrading a request
1738 nsAutoCString scheme;
1739 rv = actionURL->GetScheme(scheme);
1740 NS_ENSURE_SUCCESS(rv, rv);
1741 CopyUTF8toUTF16(scheme, *params.AppendElement());
1742
1743 CSP_LogLocalizedStr(
1744 "upgradeInsecureRequest", params,
1745 u""_ns, // aSourceFile
1746 u""_ns, // aScriptSample
1747 0, // aLineNumber
1748 0, // aColumnNumber
1749 nsIScriptError::warningFlag, "upgradeInsecureRequest"_ns,
1750 document->InnerWindowID(),
1751 !!document->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId);
1752 }
1753
1754 //
1755 // Assign to the output
1756 //
1757 actionURL.forget(aActionURL);
1758
1759 return rv;
1760 }
1761
GetDefaultSubmitElement() const1762 nsGenericHTMLFormElement* HTMLFormElement::GetDefaultSubmitElement() const {
1763 MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements ||
1764 mDefaultSubmitElement == mFirstSubmitNotInElements,
1765 "What happened here?");
1766
1767 return mDefaultSubmitElement;
1768 }
1769
IsDefaultSubmitElement(const nsGenericHTMLFormElement * aElement) const1770 bool HTMLFormElement::IsDefaultSubmitElement(
1771 const nsGenericHTMLFormElement* aElement) const {
1772 MOZ_ASSERT(aElement, "Unexpected call");
1773
1774 if (aElement == mDefaultSubmitElement) {
1775 // Yes, it is
1776 return true;
1777 }
1778
1779 if (mDefaultSubmitElement || (aElement != mFirstSubmitInElements &&
1780 aElement != mFirstSubmitNotInElements)) {
1781 // It isn't
1782 return false;
1783 }
1784
1785 // mDefaultSubmitElement is null, but we have a non-null submit around
1786 // (aElement, in fact). figure out whether it's in fact the default submit
1787 // and just hasn't been set that way yet. Note that we can't just call
1788 // HandleDefaultSubmitRemoval because we might need to notify to handle that
1789 // correctly and we don't know whether that's safe right here.
1790 if (!mFirstSubmitInElements || !mFirstSubmitNotInElements) {
1791 // We only have one first submit; aElement has to be it
1792 return true;
1793 }
1794
1795 // We have both kinds of submits. Check which comes first.
1796 nsGenericHTMLFormElement* defaultSubmit =
1797 CompareFormControlPosition(mFirstSubmitInElements,
1798 mFirstSubmitNotInElements, this) < 0
1799 ? mFirstSubmitInElements
1800 : mFirstSubmitNotInElements;
1801 return aElement == defaultSubmit;
1802 }
1803
ImplicitSubmissionIsDisabled() const1804 bool HTMLFormElement::ImplicitSubmissionIsDisabled() const {
1805 // Input text controls are always in the elements list.
1806 uint32_t numDisablingControlsFound = 0;
1807 uint32_t length = mControls->mElements.Length();
1808 for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
1809 nsCOMPtr<nsIFormControl> fc = do_QueryInterface(mControls->mElements[i]);
1810 MOZ_ASSERT(fc);
1811 if (fc->IsSingleLineTextControl(false)) {
1812 numDisablingControlsFound++;
1813 }
1814 }
1815 return numDisablingControlsFound != 1;
1816 }
1817
IsLastActiveElement(const nsGenericHTMLFormElement * aElement) const1818 bool HTMLFormElement::IsLastActiveElement(
1819 const nsGenericHTMLFormElement* aElement) const {
1820 MOZ_ASSERT(aElement, "Unexpected call");
1821
1822 for (auto* element : Reversed(mControls->mElements)) {
1823 nsCOMPtr<nsIFormControl> fc = do_QueryInterface(element);
1824 MOZ_ASSERT(fc);
1825 // XXX How about date/time control?
1826 if (fc->IsTextControl(false) && !element->IsDisabled()) {
1827 return element == aElement;
1828 }
1829 }
1830 return false;
1831 }
1832
Length()1833 int32_t HTMLFormElement::Length() { return mControls->Length(); }
1834
ForgetCurrentSubmission()1835 void HTMLFormElement::ForgetCurrentSubmission() {
1836 mNotifiedObservers = false;
1837 mTargetContext = nullptr;
1838 mCurrentLoadId = Nothing();
1839 }
1840
CheckFormValidity(nsTArray<RefPtr<Element>> * aInvalidElements) const1841 bool HTMLFormElement::CheckFormValidity(
1842 nsTArray<RefPtr<Element>>* aInvalidElements) const {
1843 bool ret = true;
1844
1845 // This shouldn't be called recursively, so use a rather large value
1846 // for the preallocated buffer.
1847 AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls;
1848 if (NS_FAILED(mControls->GetSortedControls(sortedControls))) {
1849 return false;
1850 }
1851
1852 uint32_t len = sortedControls.Length();
1853
1854 for (uint32_t i = 0; i < len; ++i) {
1855 nsCOMPtr<nsIConstraintValidation> cvElmt =
1856 do_QueryObject(sortedControls[i]);
1857 bool defaultAction = true;
1858 if (cvElmt && !cvElmt->CheckValidity(*sortedControls[i], &defaultAction)) {
1859 ret = false;
1860
1861 // Add all unhandled invalid controls to aInvalidElements if the caller
1862 // requested them.
1863 if (defaultAction && aInvalidElements) {
1864 aInvalidElements->AppendElement(sortedControls[i]);
1865 }
1866 }
1867 }
1868
1869 return ret;
1870 }
1871
CheckValidFormSubmission()1872 bool HTMLFormElement::CheckValidFormSubmission() {
1873 /**
1874 * Check for form validity: do not submit a form if there are unhandled
1875 * invalid controls in the form.
1876 * This should not be done if the form has been submitted with .submit().
1877 *
1878 * NOTE: for the moment, we are also checking that whether the MozInvalidForm
1879 * event gets prevented default so it will prevent blocking form submission if
1880 * the browser does not have implemented a UI yet.
1881 *
1882 * TODO: the check for MozInvalidForm event should be removed later when HTML5
1883 * Forms will be spread enough and authors will assume forms can't be
1884 * submitted when invalid. See bug 587671.
1885 */
1886
1887 NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate),
1888 "We shouldn't be there if novalidate is set!");
1889
1890 AutoTArray<RefPtr<Element>, 32> invalidElements;
1891 if (CheckFormValidity(&invalidElements)) {
1892 return true;
1893 }
1894
1895 // For the first invalid submission, we should update element states.
1896 // We have to do that _before_ calling the observers so we are sure they
1897 // will not interfere (like focusing the element).
1898 if (!mEverTriedInvalidSubmit) {
1899 mEverTriedInvalidSubmit = true;
1900
1901 /*
1902 * We are going to call update states assuming elements want to
1903 * be notified because we can't know.
1904 * Submissions shouldn't happen during parsing so it _should_ be safe.
1905 */
1906
1907 nsAutoScriptBlocker scriptBlocker;
1908
1909 for (uint32_t i = 0, length = mControls->mElements.Length(); i < length;
1910 ++i) {
1911 // Input elements can trigger a form submission and we want to
1912 // update the style in that case.
1913 if (mControls->mElements[i]->IsHTMLElement(nsGkAtoms::input) &&
1914 // We don't use nsContentUtils::IsFocusedContent here, because it
1915 // doesn't really do what we want for number controls: it's true
1916 // for the anonymous textnode inside, but not the number control
1917 // itself. We can use the focus state, though, because that gets
1918 // synced to the number control by the anonymous text control.
1919 mControls->mElements[i]->State().HasState(NS_EVENT_STATE_FOCUS)) {
1920 static_cast<HTMLInputElement*>(mControls->mElements[i])
1921 ->UpdateValidityUIBits(true);
1922 }
1923
1924 mControls->mElements[i]->UpdateState(true);
1925 }
1926
1927 // Because of backward compatibility, <input type='image'> is not in
1928 // elements but can be invalid.
1929 // TODO: should probably be removed when bug 606491 will be fixed.
1930 for (uint32_t i = 0, length = mControls->mNotInElements.Length();
1931 i < length; ++i) {
1932 mControls->mNotInElements[i]->UpdateState(true);
1933 }
1934 }
1935
1936 AutoJSAPI jsapi;
1937 if (!jsapi.Init(GetOwnerGlobal())) {
1938 return false;
1939 }
1940 JS::Rooted<JS::Value> detail(jsapi.cx());
1941 if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) {
1942 return false;
1943 }
1944
1945 RefPtr<CustomEvent> event =
1946 NS_NewDOMCustomEvent(OwnerDoc(), nullptr, nullptr);
1947 event->InitCustomEvent(jsapi.cx(), u"MozInvalidForm"_ns,
1948 /* CanBubble */ true,
1949 /* Cancelable */ true, detail);
1950 event->SetTrusted(true);
1951 event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
1952
1953 DispatchEvent(*event);
1954
1955 return !event->DefaultPrevented();
1956 }
1957
UpdateValidity(bool aElementValidity)1958 void HTMLFormElement::UpdateValidity(bool aElementValidity) {
1959 if (aElementValidity) {
1960 --mInvalidElementsCount;
1961 } else {
1962 ++mInvalidElementsCount;
1963 }
1964
1965 NS_ASSERTION(mInvalidElementsCount >= 0, "Something went seriously wrong!");
1966
1967 // The form validity has just changed if:
1968 // - there are no more invalid elements ;
1969 // - or there is one invalid elmement and an element just became invalid.
1970 // If we have invalid elements and we used to before as well, do nothing.
1971 if (mInvalidElementsCount &&
1972 (mInvalidElementsCount != 1 || aElementValidity)) {
1973 return;
1974 }
1975
1976 UpdateState(true);
1977 }
1978
IndexOfContent(nsIContent * aContent)1979 int32_t HTMLFormElement::IndexOfContent(nsIContent* aContent) {
1980 int32_t index = 0;
1981 return mControls->IndexOfContent(aContent, &index) == NS_OK ? index : 0;
1982 }
1983
SetCurrentRadioButton(const nsAString & aName,HTMLInputElement * aRadio)1984 void HTMLFormElement::SetCurrentRadioButton(const nsAString& aName,
1985 HTMLInputElement* aRadio) {
1986 RadioGroupManager::SetCurrentRadioButton(aName, aRadio);
1987 }
1988
GetCurrentRadioButton(const nsAString & aName)1989 HTMLInputElement* HTMLFormElement::GetCurrentRadioButton(
1990 const nsAString& aName) {
1991 return RadioGroupManager::GetCurrentRadioButton(aName);
1992 }
1993
1994 NS_IMETHODIMP
GetNextRadioButton(const nsAString & aName,const bool aPrevious,HTMLInputElement * aFocusedRadio,HTMLInputElement ** aRadioOut)1995 HTMLFormElement::GetNextRadioButton(const nsAString& aName,
1996 const bool aPrevious,
1997 HTMLInputElement* aFocusedRadio,
1998 HTMLInputElement** aRadioOut) {
1999 return RadioGroupManager::GetNextRadioButton(aName, aPrevious, aFocusedRadio,
2000 aRadioOut);
2001 }
2002
2003 NS_IMETHODIMP
WalkRadioGroup(const nsAString & aName,nsIRadioVisitor * aVisitor)2004 HTMLFormElement::WalkRadioGroup(const nsAString& aName,
2005 nsIRadioVisitor* aVisitor) {
2006 return RadioGroupManager::WalkRadioGroup(aName, aVisitor);
2007 }
2008
AddToRadioGroup(const nsAString & aName,HTMLInputElement * aRadio)2009 void HTMLFormElement::AddToRadioGroup(const nsAString& aName,
2010 HTMLInputElement* aRadio) {
2011 RadioGroupManager::AddToRadioGroup(aName, aRadio);
2012 }
2013
RemoveFromRadioGroup(const nsAString & aName,HTMLInputElement * aRadio)2014 void HTMLFormElement::RemoveFromRadioGroup(const nsAString& aName,
2015 HTMLInputElement* aRadio) {
2016 RadioGroupManager::RemoveFromRadioGroup(aName, aRadio);
2017 }
2018
GetRequiredRadioCount(const nsAString & aName) const2019 uint32_t HTMLFormElement::GetRequiredRadioCount(const nsAString& aName) const {
2020 return RadioGroupManager::GetRequiredRadioCount(aName);
2021 }
2022
RadioRequiredWillChange(const nsAString & aName,bool aRequiredAdded)2023 void HTMLFormElement::RadioRequiredWillChange(const nsAString& aName,
2024 bool aRequiredAdded) {
2025 RadioGroupManager::RadioRequiredWillChange(aName, aRequiredAdded);
2026 }
2027
GetValueMissingState(const nsAString & aName) const2028 bool HTMLFormElement::GetValueMissingState(const nsAString& aName) const {
2029 return RadioGroupManager::GetValueMissingState(aName);
2030 }
2031
SetValueMissingState(const nsAString & aName,bool aValue)2032 void HTMLFormElement::SetValueMissingState(const nsAString& aName,
2033 bool aValue) {
2034 RadioGroupManager::SetValueMissingState(aName, aValue);
2035 }
2036
IntrinsicState() const2037 EventStates HTMLFormElement::IntrinsicState() const {
2038 EventStates state = nsGenericHTMLElement::IntrinsicState();
2039
2040 if (mInvalidElementsCount) {
2041 state |= NS_EVENT_STATE_INVALID;
2042 } else {
2043 state |= NS_EVENT_STATE_VALID;
2044 }
2045
2046 return state;
2047 }
2048
Clear()2049 void HTMLFormElement::Clear() {
2050 for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
2051 mImageElements[i]->ClearForm(false);
2052 }
2053 mImageElements.Clear();
2054 mImageNameLookupTable.Clear();
2055 mPastNameLookupTable.Clear();
2056 }
2057
2058 namespace {
2059
2060 struct PositionComparator {
2061 nsIContent* const mElement;
PositionComparatormozilla::dom::__anon0cfbb8960211::PositionComparator2062 explicit PositionComparator(nsIContent* const aElement)
2063 : mElement(aElement) {}
2064
operator ()mozilla::dom::__anon0cfbb8960211::PositionComparator2065 int operator()(nsIContent* aElement) const {
2066 if (mElement == aElement) {
2067 return 0;
2068 }
2069 if (nsContentUtils::PositionIsBefore(mElement, aElement)) {
2070 return -1;
2071 }
2072 return 1;
2073 }
2074 };
2075
2076 struct RadioNodeListAdaptor {
2077 RadioNodeList* const mList;
RadioNodeListAdaptormozilla::dom::__anon0cfbb8960211::RadioNodeListAdaptor2078 explicit RadioNodeListAdaptor(RadioNodeList* aList) : mList(aList) {}
operator []mozilla::dom::__anon0cfbb8960211::RadioNodeListAdaptor2079 nsIContent* operator[](size_t aIdx) const { return mList->Item(aIdx); }
2080 };
2081
2082 } // namespace
2083
AddElementToTableInternal(nsInterfaceHashtable<nsStringHashKey,nsISupports> & aTable,nsIContent * aChild,const nsAString & aName)2084 nsresult HTMLFormElement::AddElementToTableInternal(
2085 nsInterfaceHashtable<nsStringHashKey, nsISupports>& aTable,
2086 nsIContent* aChild, const nsAString& aName) {
2087 return aTable.WithEntryHandle(aName, [&](auto&& entry) {
2088 if (!entry) {
2089 // No entry found, add the element
2090 entry.Insert(aChild);
2091 ++mExpandoAndGeneration.generation;
2092 } else {
2093 // Found something in the hash, check its type
2094 nsCOMPtr<nsIContent> content = do_QueryInterface(entry.Data());
2095
2096 if (content) {
2097 // Check if the new content is the same as the one we found in the
2098 // hash, if it is then we leave it in the hash as it is, this will
2099 // happen if a form control has both a name and an id with the same
2100 // value
2101 if (content == aChild) {
2102 return NS_OK;
2103 }
2104
2105 // Found an element, create a list, add the element to the list and put
2106 // the list in the hash
2107 RadioNodeList* list = new RadioNodeList(this);
2108
2109 // If an element has a @form, we can assume it *might* be able to not
2110 // have a parent and still be in the form.
2111 NS_ASSERTION(
2112 (content->IsElement() && content->AsElement()->HasAttr(
2113 kNameSpaceID_None, nsGkAtoms::form)) ||
2114 content->GetParent(),
2115 "Item in list without parent");
2116
2117 // Determine the ordering between the new and old element.
2118 bool newFirst = nsContentUtils::PositionIsBefore(aChild, content);
2119
2120 list->AppendElement(newFirst ? aChild : content.get());
2121 list->AppendElement(newFirst ? content.get() : aChild);
2122
2123 nsCOMPtr<nsISupports> listSupports = do_QueryObject(list);
2124
2125 // Replace the element with the list.
2126 entry.Data() = listSupports;
2127 } else {
2128 // There's already a list in the hash, add the child to the list.
2129 MOZ_ASSERT(nsCOMPtr<RadioNodeList>(do_QueryInterface(entry.Data())));
2130 auto* list = static_cast<RadioNodeList*>(entry->get());
2131
2132 NS_ASSERTION(
2133 list->Length() > 1,
2134 "List should have been converted back to a single element");
2135
2136 // Fast-path appends; this check is ok even if the child is
2137 // already in the list, since if it tests true the child would
2138 // have come at the end of the list, and the PositionIsBefore
2139 // will test false.
2140 if (nsContentUtils::PositionIsBefore(list->Item(list->Length() - 1),
2141 aChild)) {
2142 list->AppendElement(aChild);
2143 return NS_OK;
2144 }
2145
2146 // If a control has a name equal to its id, it could be in the
2147 // list already.
2148 if (list->IndexOf(aChild) != -1) {
2149 return NS_OK;
2150 }
2151
2152 size_t idx;
2153 DebugOnly<bool> found =
2154 BinarySearchIf(RadioNodeListAdaptor(list), 0, list->Length(),
2155 PositionComparator(aChild), &idx);
2156 MOZ_ASSERT(!found, "should not have found an element");
2157
2158 list->InsertElementAt(aChild, idx);
2159 }
2160 }
2161
2162 return NS_OK;
2163 });
2164 }
2165
AddImageElement(HTMLImageElement * aChild)2166 nsresult HTMLFormElement::AddImageElement(HTMLImageElement* aChild) {
2167 AddElementToList(mImageElements, aChild, this);
2168 return NS_OK;
2169 }
2170
AddImageElementToTable(HTMLImageElement * aChild,const nsAString & aName)2171 nsresult HTMLFormElement::AddImageElementToTable(HTMLImageElement* aChild,
2172 const nsAString& aName) {
2173 return AddElementToTableInternal(mImageNameLookupTable, aChild, aName);
2174 }
2175
RemoveImageElement(HTMLImageElement * aChild)2176 nsresult HTMLFormElement::RemoveImageElement(HTMLImageElement* aChild) {
2177 RemoveElementFromPastNamesMap(aChild);
2178
2179 size_t index = mImageElements.IndexOf(aChild);
2180 NS_ENSURE_STATE(index != mImageElements.NoIndex);
2181
2182 mImageElements.RemoveElementAt(index);
2183 return NS_OK;
2184 }
2185
RemoveImageElementFromTable(HTMLImageElement * aElement,const nsAString & aName)2186 nsresult HTMLFormElement::RemoveImageElementFromTable(
2187 HTMLImageElement* aElement, const nsAString& aName) {
2188 return RemoveElementFromTableInternal(mImageNameLookupTable, aElement, aName);
2189 }
2190
AddToPastNamesMap(const nsAString & aName,nsISupports * aChild)2191 void HTMLFormElement::AddToPastNamesMap(const nsAString& aName,
2192 nsISupports* aChild) {
2193 // If candidates contains exactly one node. Add a mapping from name to the
2194 // node in candidates in the form element's past names map, replacing the
2195 // previous entry with the same name, if any.
2196 nsCOMPtr<nsIContent> node = do_QueryInterface(aChild);
2197 if (node) {
2198 mPastNameLookupTable.InsertOrUpdate(aName, ToSupports(node));
2199 node->SetFlags(MAY_BE_IN_PAST_NAMES_MAP);
2200 }
2201 }
2202
RemoveElementFromPastNamesMap(Element * aElement)2203 void HTMLFormElement::RemoveElementFromPastNamesMap(Element* aElement) {
2204 if (!aElement->HasFlag(MAY_BE_IN_PAST_NAMES_MAP)) {
2205 return;
2206 }
2207
2208 aElement->UnsetFlags(MAY_BE_IN_PAST_NAMES_MAP);
2209
2210 uint32_t oldCount = mPastNameLookupTable.Count();
2211 for (auto iter = mPastNameLookupTable.Iter(); !iter.Done(); iter.Next()) {
2212 if (aElement == iter.Data()) {
2213 iter.Remove();
2214 }
2215 }
2216 if (oldCount != mPastNameLookupTable.Count()) {
2217 ++mExpandoAndGeneration.generation;
2218 }
2219 }
2220
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)2221 JSObject* HTMLFormElement::WrapNode(JSContext* aCx,
2222 JS::Handle<JSObject*> aGivenProto) {
2223 return HTMLFormElement_Binding::Wrap(aCx, this, aGivenProto);
2224 }
2225
GetFormNumberForStateKey()2226 int32_t HTMLFormElement::GetFormNumberForStateKey() {
2227 if (mFormNumber == -1) {
2228 mFormNumber = OwnerDoc()->GetNextFormNumber();
2229 }
2230 return mFormNumber;
2231 }
2232
NodeInfoChanged(Document * aOldDoc)2233 void HTMLFormElement::NodeInfoChanged(Document* aOldDoc) {
2234 nsGenericHTMLElement::NodeInfoChanged(aOldDoc);
2235
2236 // When a <form> element is adopted into a new document, we want any state
2237 // keys generated from it to no longer consider this element to be parser
2238 // inserted, and so have state keys based on the position of the <form>
2239 // element in the document, rather than the order it was inserted in.
2240 //
2241 // This is not strictly necessary, since we only ever look at the form number
2242 // for parser inserted form controls, and we do that at the time the form
2243 // control element is inserted into its original document by the parser.
2244 mFormNumber = -1;
2245 }
2246
IsSubmitting() const2247 bool HTMLFormElement::IsSubmitting() const {
2248 bool loading = mTargetContext && !mTargetContext->IsDiscarded() &&
2249 mCurrentLoadId &&
2250 mTargetContext->IsLoadingIdentifier(*mCurrentLoadId);
2251 return loading;
2252 }
2253
MaybeFireFormRemoved()2254 void HTMLFormElement::MaybeFireFormRemoved() {
2255 // We want this event to be fired only when the form is removed from the DOM
2256 // tree, not when it is released (ex, tab is closed). So don't fire an event
2257 // when the form doesn't have a docshell.
2258 Document* doc = GetComposedDoc();
2259 nsIDocShell* container = doc ? doc->GetDocShell() : nullptr;
2260 if (!container) {
2261 return;
2262 }
2263
2264 // Right now, only the password manager listens to the event and only listen
2265 // to it under certain circumstances. So don't fire this event unless
2266 // necessary.
2267 if (!doc->ShouldNotifyFormOrPasswordRemoved()) {
2268 return;
2269 }
2270
2271 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
2272 this, u"DOMFormRemoved"_ns, CanBubble::eNo, ChromeOnlyDispatch::eYes);
2273 asyncDispatcher->RunDOMEventWhenSafe();
2274 }
2275
2276 } // namespace mozilla::dom
2277