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