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 "jsapi.h"
10 #include "mozilla/ContentEvents.h"
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/EventStateManager.h"
13 #include "mozilla/EventStates.h"
14 #include "mozilla/dom/AutocompleteErrorEvent.h"
15 #include "mozilla/dom/nsCSPUtils.h"
16 #include "mozilla/dom/nsCSPContext.h"
17 #include "mozilla/dom/HTMLFormControlsCollection.h"
18 #include "mozilla/dom/HTMLFormElementBinding.h"
19 #include "mozilla/Move.h"
20 #include "nsIHTMLDocument.h"
21 #include "nsGkAtoms.h"
22 #include "nsStyleConsts.h"
23 #include "nsPresContext.h"
24 #include "nsIDocument.h"
25 #include "nsIFormControlFrame.h"
26 #include "nsError.h"
27 #include "nsContentUtils.h"
28 #include "nsInterfaceHashtable.h"
29 #include "nsContentList.h"
30 #include "nsCOMArray.h"
31 #include "nsAutoPtr.h"
32 #include "nsTArray.h"
33 #include "nsIMutableArray.h"
34 #include "nsIFormAutofillContentService.h"
35 #include "mozilla/BinarySearch.h"
36 #include "nsQueryObject.h"
37 
38 // form submission
39 #include "HTMLFormSubmissionConstants.h"
40 #include "mozilla/dom/FormData.h"
41 #include "mozilla/Telemetry.h"
42 #include "nsIFormSubmitObserver.h"
43 #include "nsIObserverService.h"
44 #include "nsICategoryManager.h"
45 #include "nsCategoryManagerUtils.h"
46 #include "nsISimpleEnumerator.h"
47 #include "nsRange.h"
48 #include "nsIScriptError.h"
49 #include "nsIScriptSecurityManager.h"
50 #include "nsNetUtil.h"
51 #include "nsIInterfaceRequestorUtils.h"
52 #include "nsIWebProgress.h"
53 #include "nsIDocShell.h"
54 #include "nsIPrompt.h"
55 #include "nsISecurityUITelemetry.h"
56 #include "nsIStringBundle.h"
57 
58 // radio buttons
59 #include "mozilla/dom/HTMLInputElement.h"
60 #include "nsIRadioVisitor.h"
61 #include "RadioNodeList.h"
62 
63 #include "nsLayoutUtils.h"
64 
65 #include "mozAutoDocUpdate.h"
66 #include "nsIHTMLCollection.h"
67 
68 #include "nsIConstraintValidation.h"
69 
70 #include "nsIDOMHTMLButtonElement.h"
71 #include "nsSandboxFlags.h"
72 
73 #include "nsIContentSecurityPolicy.h"
74 
75 // images
76 #include "mozilla/dom/HTMLImageElement.h"
77 
78 // construction, destruction
79 NS_IMPL_NS_NEW_HTML_ELEMENT(Form)
80 
81 namespace mozilla {
82 namespace dom {
83 
84 static const uint8_t NS_FORM_AUTOCOMPLETE_ON  = 1;
85 static const uint8_t NS_FORM_AUTOCOMPLETE_OFF = 0;
86 
87 static const nsAttrValue::EnumTable kFormAutocompleteTable[] = {
88   { "on",  NS_FORM_AUTOCOMPLETE_ON },
89   { "off", NS_FORM_AUTOCOMPLETE_OFF },
90   { nullptr, 0 }
91 };
92 // Default autocomplete value is 'on'.
93 static const nsAttrValue::EnumTable* kFormDefaultAutocomplete = &kFormAutocompleteTable[0];
94 
95 bool HTMLFormElement::gFirstFormSubmitted = false;
96 bool HTMLFormElement::gPasswordManagerInitialized = false;
97 
HTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo> & aNodeInfo)98 HTMLFormElement::HTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
99   : nsGenericHTMLElement(aNodeInfo),
100     mControls(new HTMLFormControlsCollection(this)),
101     mSelectedRadioButtons(2),
102     mRequiredRadioButtonCounts(2),
103     mValueMissingRadioGroups(2),
104     mGeneratingSubmit(false),
105     mGeneratingReset(false),
106     mIsSubmitting(false),
107     mDeferSubmission(false),
108     mNotifiedObservers(false),
109     mNotifiedObserversResult(false),
110     mSubmitPopupState(openAbused),
111     mSubmitInitiatedFromUserInput(false),
112     mPendingSubmission(nullptr),
113     mSubmittingRequest(nullptr),
114     mDefaultSubmitElement(nullptr),
115     mFirstSubmitInElements(nullptr),
116     mFirstSubmitNotInElements(nullptr),
117     mImageNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
118     mPastNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
119     mInvalidElementsCount(0),
120     mEverTriedInvalidSubmit(false)
121 {
122   // We start out valid.
123   AddStatesSilently(NS_EVENT_STATE_VALID);
124 }
125 
~HTMLFormElement()126 HTMLFormElement::~HTMLFormElement()
127 {
128   if (mControls) {
129     mControls->DropFormReference();
130   }
131 
132   Clear();
133 }
134 
135 // nsISupports
136 
137 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormElement)
138 
139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLFormElement,
140                                                   nsGenericHTMLElement)
141   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControls)
142   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable)
143   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
144   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedRadioButtons)
145 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
146 
147 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
148                                                 nsGenericHTMLElement)
149   tmp->Clear();
150   tmp->mExpandoAndGeneration.OwnerUnlinked();
151 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
152 
NS_IMPL_ADDREF_INHERITED(HTMLFormElement,Element)153 NS_IMPL_ADDREF_INHERITED(HTMLFormElement, Element)
154 NS_IMPL_RELEASE_INHERITED(HTMLFormElement, Element)
155 
156 
157 // QueryInterface implementation for HTMLFormElement
158 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLFormElement)
159   NS_INTERFACE_TABLE_INHERITED(HTMLFormElement,
160                                nsIDOMHTMLFormElement,
161                                nsIForm,
162                                nsIWebProgressListener,
163                                nsIRadioGroupContainer)
164 NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
165 
166 // EventTarget
167 void
168 HTMLFormElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
169 {
170   if (mFormPasswordEventDispatcher == aEvent) {
171     mFormPasswordEventDispatcher = nullptr;
172   }
173 }
174 
175 // nsIDOMHTMLFormElement
176 
NS_IMPL_ELEMENT_CLONE(HTMLFormElement)177 NS_IMPL_ELEMENT_CLONE(HTMLFormElement)
178 
179 nsIHTMLCollection*
180 HTMLFormElement::Elements()
181 {
182   return mControls;
183 }
184 
185 NS_IMETHODIMP
GetElements(nsIDOMHTMLCollection ** aElements)186 HTMLFormElement::GetElements(nsIDOMHTMLCollection** aElements)
187 {
188   *aElements = Elements();
189   NS_ADDREF(*aElements);
190   return NS_OK;
191 }
192 
193 nsresult
SetAttr(int32_t aNameSpaceID,nsIAtom * aName,nsIAtom * aPrefix,const nsAString & aValue,bool aNotify)194 HTMLFormElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
195                          nsIAtom* aPrefix, const nsAString& aValue,
196                          bool aNotify)
197 {
198   if ((aName == nsGkAtoms::action || aName == nsGkAtoms::target) &&
199       aNameSpaceID == kNameSpaceID_None) {
200     if (mPendingSubmission) {
201       // aha, there is a pending submission that means we're in
202       // the script and we need to flush it. let's tell it
203       // that the event was ignored to force the flush.
204       // the second argument is not playing a role at all.
205       FlushPendingSubmission();
206     }
207     // Don't forget we've notified the password manager already if the
208     // page sets the action/target in the during submit. (bug 343182)
209     bool notifiedObservers = mNotifiedObservers;
210     ForgetCurrentSubmission();
211     mNotifiedObservers = notifiedObservers;
212   }
213   return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
214                                        aNotify);
215 }
216 
217 nsresult
AfterSetAttr(int32_t aNameSpaceID,nsIAtom * aName,const nsAttrValue * aValue,bool aNotify)218 HTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
219                               const nsAttrValue* aValue, bool aNotify)
220 {
221   if (aName == nsGkAtoms::novalidate && aNameSpaceID == kNameSpaceID_None) {
222     // Update all form elements states because they might be [no longer]
223     // affected by :-moz-ui-valid or :-moz-ui-invalid.
224     for (uint32_t i = 0, length = mControls->mElements.Length();
225          i < length; ++i) {
226       mControls->mElements[i]->UpdateState(true);
227     }
228 
229     for (uint32_t i = 0, length = mControls->mNotInElements.Length();
230          i < length; ++i) {
231       mControls->mNotInElements[i]->UpdateState(true);
232     }
233   }
234 
235   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify);
236 }
237 
NS_IMPL_STRING_ATTR(HTMLFormElement,AcceptCharset,acceptcharset)238 NS_IMPL_STRING_ATTR(HTMLFormElement, AcceptCharset, acceptcharset)
239 NS_IMPL_ACTION_ATTR(HTMLFormElement, Action, action)
240 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Autocomplete, autocomplete,
241                                 kFormDefaultAutocomplete->tag)
242 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Enctype, enctype,
243                                 kFormDefaultEnctype->tag)
244 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Method, method,
245                                 kFormDefaultMethod->tag)
246 NS_IMPL_BOOL_ATTR(HTMLFormElement, NoValidate, novalidate)
247 NS_IMPL_STRING_ATTR(HTMLFormElement, Name, name)
248 NS_IMPL_STRING_ATTR(HTMLFormElement, Target, target)
249 
250 void
251 HTMLFormElement::Submit(ErrorResult& aRv)
252 {
253   // Send the submit event
254   if (mPendingSubmission) {
255     // aha, we have a pending submission that was not flushed
256     // (this happens when form.submit() is called twice)
257     // we have to delete it and build a new one since values
258     // might have changed inbetween (we emulate IE here, that's all)
259     mPendingSubmission = nullptr;
260   }
261 
262   aRv = DoSubmitOrReset(nullptr, eFormSubmit);
263 }
264 
265 NS_IMETHODIMP
Submit()266 HTMLFormElement::Submit()
267 {
268   ErrorResult rv;
269   Submit(rv);
270   return rv.StealNSResult();
271 }
272 
273 NS_IMETHODIMP
Reset()274 HTMLFormElement::Reset()
275 {
276   InternalFormEvent event(true, eFormReset);
277   EventDispatcher::Dispatch(static_cast<nsIContent*>(this), nullptr, &event);
278   return NS_OK;
279 }
280 
281 NS_IMETHODIMP
CheckValidity(bool * retVal)282 HTMLFormElement::CheckValidity(bool* retVal)
283 {
284   *retVal = CheckValidity();
285   return NS_OK;
286 }
287 
288 void
RequestAutocomplete()289 HTMLFormElement::RequestAutocomplete()
290 {
291   bool dummy;
292   nsCOMPtr<nsIDOMWindow> window =
293     do_QueryInterface(OwnerDoc()->GetScriptHandlingObject(dummy));
294   nsCOMPtr<nsIFormAutofillContentService> formAutofillContentService =
295     do_GetService("@mozilla.org/formautofill/content-service;1");
296 
297   if (!formAutofillContentService || !window) {
298     AutocompleteErrorEventInit init;
299     init.mBubbles = true;
300     init.mCancelable = false;
301     init.mReason = AutoCompleteErrorReason::Disabled;
302 
303     RefPtr<AutocompleteErrorEvent> event =
304       AutocompleteErrorEvent::Constructor(this, NS_LITERAL_STRING("autocompleteerror"), init);
305 
306     (new AsyncEventDispatcher(this, event))->PostDOMEvent();
307     return;
308   }
309 
310   formAutofillContentService->RequestAutocomplete(this, window);
311 }
312 
313 bool
ParseAttribute(int32_t aNamespaceID,nsIAtom * aAttribute,const nsAString & aValue,nsAttrValue & aResult)314 HTMLFormElement::ParseAttribute(int32_t aNamespaceID,
315                                 nsIAtom* aAttribute,
316                                 const nsAString& aValue,
317                                 nsAttrValue& aResult)
318 {
319   if (aNamespaceID == kNameSpaceID_None) {
320     if (aAttribute == nsGkAtoms::method) {
321       return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
322     }
323     if (aAttribute == nsGkAtoms::enctype) {
324       return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
325     }
326     if (aAttribute == nsGkAtoms::autocomplete) {
327       return aResult.ParseEnumValue(aValue, kFormAutocompleteTable, false);
328     }
329   }
330 
331   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
332                                               aResult);
333 }
334 
335 nsresult
BindToTree(nsIDocument * aDocument,nsIContent * aParent,nsIContent * aBindingParent,bool aCompileEventHandlers)336 HTMLFormElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
337                             nsIContent* aBindingParent,
338                             bool aCompileEventHandlers)
339 {
340   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
341                                                  aBindingParent,
342                                                  aCompileEventHandlers);
343   NS_ENSURE_SUCCESS(rv, rv);
344 
345   nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(aDocument));
346   if (htmlDoc) {
347     htmlDoc->AddedForm();
348   }
349 
350   return rv;
351 }
352 
353 template<typename T>
354 static void
MarkOrphans(const nsTArray<T * > & aArray)355 MarkOrphans(const nsTArray<T*>& aArray)
356 {
357   uint32_t length = aArray.Length();
358   for (uint32_t i = 0; i < length; ++i) {
359     aArray[i]->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
360   }
361 }
362 
363 static void
CollectOrphans(nsINode * aRemovalRoot,const nsTArray<nsGenericHTMLFormElement * > & aArray,nsIDOMHTMLFormElement * aThisForm)364 CollectOrphans(nsINode* aRemovalRoot,
365                const nsTArray<nsGenericHTMLFormElement*>& aArray
366 #ifdef DEBUG
367                , nsIDOMHTMLFormElement* aThisForm
368 #endif
369                )
370 {
371   // Put a script blocker around all the notifications we're about to do.
372   nsAutoScriptBlocker scriptBlocker;
373 
374   // Walk backwards so that if we remove elements we can just keep iterating
375   uint32_t length = aArray.Length();
376   for (uint32_t i = length; i > 0; --i) {
377     nsGenericHTMLFormElement* node = aArray[i-1];
378 
379     // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
380     // node is in fact a descendant of the form and hence should stay in the
381     // form.  If it _is_ set, then we need to check whether the node is a
382     // descendant of aRemovalRoot.  If it is, we leave it in the form.
383 #ifdef DEBUG
384     bool removed = false;
385 #endif
386     if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
387       node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
388       if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
389         node->ClearForm(true);
390 
391         // When a form control loses its form owner, its state can change.
392         node->UpdateState(true);
393 #ifdef DEBUG
394         removed = true;
395 #endif
396       }
397     }
398 
399 #ifdef DEBUG
400     if (!removed) {
401       nsCOMPtr<nsIDOMHTMLFormElement> form;
402       node->GetForm(getter_AddRefs(form));
403       NS_ASSERTION(form == aThisForm, "How did that happen?");
404     }
405 #endif /* DEBUG */
406   }
407 }
408 
409 static void
CollectOrphans(nsINode * aRemovalRoot,const nsTArray<HTMLImageElement * > & aArray,nsIDOMHTMLFormElement * aThisForm)410 CollectOrphans(nsINode* aRemovalRoot,
411                const nsTArray<HTMLImageElement*>& aArray
412 #ifdef DEBUG
413                , nsIDOMHTMLFormElement* aThisForm
414 #endif
415                )
416 {
417   // Walk backwards so that if we remove elements we can just keep iterating
418   uint32_t length = aArray.Length();
419   for (uint32_t i = length; i > 0; --i) {
420     HTMLImageElement* node = aArray[i-1];
421 
422     // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
423     // node is in fact a descendant of the form and hence should stay in the
424     // form.  If it _is_ set, then we need to check whether the node is a
425     // descendant of aRemovalRoot.  If it is, we leave it in the form.
426 #ifdef DEBUG
427     bool removed = false;
428 #endif
429     if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
430       node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
431       if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
432         node->ClearForm(true);
433 
434 #ifdef DEBUG
435         removed = true;
436 #endif
437       }
438     }
439 
440 #ifdef DEBUG
441     if (!removed) {
442       nsCOMPtr<nsIDOMHTMLFormElement> form = node->GetForm();
443       NS_ASSERTION(form == aThisForm, "How did that happen?");
444     }
445 #endif /* DEBUG */
446   }
447 }
448 
449 void
UnbindFromTree(bool aDeep,bool aNullParent)450 HTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent)
451 {
452   nsCOMPtr<nsIHTMLDocument> oldDocument = do_QueryInterface(GetUncomposedDoc());
453 
454   // Mark all of our controls as maybe being orphans
455   MarkOrphans(mControls->mElements);
456   MarkOrphans(mControls->mNotInElements);
457   MarkOrphans(mImageElements);
458 
459   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
460 
461   nsINode* ancestor = this;
462   nsINode* cur;
463   do {
464     cur = ancestor->GetParentNode();
465     if (!cur) {
466       break;
467     }
468     ancestor = cur;
469   } while (1);
470 
471   CollectOrphans(ancestor, mControls->mElements
472 #ifdef DEBUG
473                  , this
474 #endif
475                  );
476   CollectOrphans(ancestor, mControls->mNotInElements
477 #ifdef DEBUG
478                  , this
479 #endif
480                  );
481   CollectOrphans(ancestor, mImageElements
482 #ifdef DEBUG
483                  , this
484 #endif
485                  );
486 
487   if (oldDocument) {
488     oldDocument->RemovedForm();
489   }
490   ForgetCurrentSubmission();
491 }
492 
493 nsresult
PreHandleEvent(EventChainPreVisitor & aVisitor)494 HTMLFormElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
495 {
496   aVisitor.mWantsWillHandleEvent = true;
497   if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
498     uint32_t msg = aVisitor.mEvent->mMessage;
499     if (msg == eFormSubmit) {
500       if (mGeneratingSubmit) {
501         aVisitor.mCanHandle = false;
502         return NS_OK;
503       }
504       mGeneratingSubmit = true;
505 
506       // let the form know that it needs to defer the submission,
507       // that means that if there are scripted submissions, the
508       // latest one will be deferred until after the exit point of the handler.
509       mDeferSubmission = true;
510     } else if (msg == eFormReset) {
511       if (mGeneratingReset) {
512         aVisitor.mCanHandle = false;
513         return NS_OK;
514       }
515       mGeneratingReset = true;
516     }
517   }
518   return nsGenericHTMLElement::PreHandleEvent(aVisitor);
519 }
520 
521 nsresult
WillHandleEvent(EventChainPostVisitor & aVisitor)522 HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor)
523 {
524   // If this is the bubble stage and there is a nested form below us which
525   // received a submit event we do *not* want to handle the submit event
526   // for this form too.
527   if ((aVisitor.mEvent->mMessage == eFormSubmit ||
528        aVisitor.mEvent->mMessage == eFormReset) &&
529       aVisitor.mEvent->mFlags.mInBubblingPhase &&
530       aVisitor.mEvent->mOriginalTarget != static_cast<nsIContent*>(this)) {
531     aVisitor.mEvent->StopPropagation();
532   }
533   return NS_OK;
534 }
535 
536 nsresult
PostHandleEvent(EventChainPostVisitor & aVisitor)537 HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
538 {
539   if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
540     EventMessage msg = aVisitor.mEvent->mMessage;
541     if (msg == eFormSubmit) {
542       // let the form know not to defer subsequent submissions
543       mDeferSubmission = false;
544     }
545 
546     if (aVisitor.mEventStatus == nsEventStatus_eIgnore) {
547       switch (msg) {
548         case eFormReset:
549         case eFormSubmit: {
550           if (mPendingSubmission && msg == eFormSubmit) {
551             // tell the form to forget a possible pending submission.
552             // the reason is that the script returned true (the event was
553             // ignored) so if there is a stored submission, it will miss
554             // the name/value of the submitting element, thus we need
555             // to forget it and the form element will build a new one
556             mPendingSubmission = nullptr;
557           }
558           DoSubmitOrReset(aVisitor.mEvent, msg);
559           break;
560         }
561         default:
562           break;
563       }
564     } else {
565       if (msg == eFormSubmit) {
566         // tell the form to flush a possible pending submission.
567         // the reason is that the script returned false (the event was
568         // not ignored) so if there is a stored submission, it needs to
569         // be submitted immediatelly.
570         FlushPendingSubmission();
571       }
572     }
573 
574     if (msg == eFormSubmit) {
575       mGeneratingSubmit = false;
576     } else if (msg == eFormReset) {
577       mGeneratingReset = false;
578     }
579   }
580   return NS_OK;
581 }
582 
583 nsresult
DoSubmitOrReset(WidgetEvent * aEvent,EventMessage aMessage)584 HTMLFormElement::DoSubmitOrReset(WidgetEvent* aEvent,
585                                  EventMessage aMessage)
586 {
587   // Make sure the presentation is up-to-date
588   nsIDocument* doc = GetComposedDoc();
589   if (doc) {
590     doc->FlushPendingNotifications(Flush_ContentAndNotify);
591   }
592 
593   // JBK Don't get form frames anymore - bug 34297
594 
595   // Submit or Reset the form
596   if (eFormReset == aMessage) {
597     return DoReset();
598   }
599 
600   if (eFormSubmit == aMessage) {
601     // Don't submit if we're not in a document or if we're in
602     // a sandboxed frame and form submit is disabled.
603     if (!doc || (doc->GetSandboxFlags() & SANDBOXED_FORMS)) {
604       return NS_OK;
605     }
606     return DoSubmit(aEvent);
607   }
608 
609   MOZ_ASSERT(false);
610   return NS_OK;
611 }
612 
613 nsresult
DoReset()614 HTMLFormElement::DoReset()
615 {
616   // JBK walk the elements[] array instead of form frame controls - bug 34297
617   uint32_t numElements = GetElementCount();
618   for (uint32_t elementX = 0; elementX < numElements; ++elementX) {
619     // Hold strong ref in case the reset does something weird
620     nsCOMPtr<nsIFormControl> controlNode = GetElementAt(elementX);
621     if (controlNode) {
622       controlNode->Reset();
623     }
624   }
625 
626   return NS_OK;
627 }
628 
629 #define NS_ENSURE_SUBMIT_SUCCESS(rv)                                          \
630   if (NS_FAILED(rv)) {                                                        \
631     ForgetCurrentSubmission();                                                \
632     return rv;                                                                \
633   }
634 
635 nsresult
DoSubmit(WidgetEvent * aEvent)636 HTMLFormElement::DoSubmit(WidgetEvent* aEvent)
637 {
638   NS_ASSERTION(GetComposedDoc(), "Should never get here without a current doc");
639 
640   if (mIsSubmitting) {
641     NS_WARNING("Preventing double form submission");
642     // XXX Should this return an error?
643     return NS_OK;
644   }
645 
646   // Mark us as submitting so that we don't try to submit again
647   mIsSubmitting = true;
648   NS_ASSERTION(!mWebProgress && !mSubmittingRequest, "Web progress / submitting request should not exist here!");
649 
650   nsAutoPtr<HTMLFormSubmission> submission;
651 
652   //
653   // prepare the submission object
654   //
655   nsresult rv = BuildSubmission(getter_Transfers(submission), aEvent);
656   if (NS_FAILED(rv)) {
657     mIsSubmitting = false;
658     return rv;
659   }
660 
661   // XXXbz if the script global is that for an sXBL/XBL2 doc, it won't
662   // be a window...
663   nsPIDOMWindowOuter *window = OwnerDoc()->GetWindow();
664 
665   if (window) {
666     mSubmitPopupState = window->GetPopupControlState();
667   } else {
668     mSubmitPopupState = openAbused;
669   }
670 
671   mSubmitInitiatedFromUserInput = EventStateManager::IsHandlingUserInput();
672 
673   if(mDeferSubmission) {
674     // we are in an event handler, JS submitted so we have to
675     // defer this submission. let's remember it and return
676     // without submitting
677     mPendingSubmission = submission;
678     // ensure reentrancy
679     mIsSubmitting = false;
680     return NS_OK;
681   }
682 
683   //
684   // perform the submission
685   //
686   return SubmitSubmission(submission);
687 }
688 
689 nsresult
BuildSubmission(HTMLFormSubmission ** aFormSubmission,WidgetEvent * aEvent)690 HTMLFormElement::BuildSubmission(HTMLFormSubmission** aFormSubmission,
691                                  WidgetEvent* aEvent)
692 {
693   NS_ASSERTION(!mPendingSubmission, "tried to build two submissions!");
694 
695   // Get the originating frame (failure is non-fatal)
696   nsGenericHTMLElement* originatingElement = nullptr;
697   if (aEvent) {
698     InternalFormEvent* formEvent = aEvent->AsFormEvent();
699     if (formEvent) {
700       nsIContent* originator = formEvent->mOriginator;
701       if (originator) {
702         if (!originator->IsHTMLElement()) {
703           return NS_ERROR_UNEXPECTED;
704         }
705         originatingElement = static_cast<nsGenericHTMLElement*>(originator);
706       }
707     }
708   }
709 
710   nsresult rv;
711 
712   //
713   // Get the submission object
714   //
715   rv = HTMLFormSubmission::GetFromForm(this, originatingElement,
716                                        aFormSubmission);
717   NS_ENSURE_SUBMIT_SUCCESS(rv);
718 
719   //
720   // Dump the data into the submission object
721   //
722   rv = WalkFormElements(*aFormSubmission);
723   NS_ENSURE_SUBMIT_SUCCESS(rv);
724 
725   return NS_OK;
726 }
727 
728 nsresult
SubmitSubmission(HTMLFormSubmission * aFormSubmission)729 HTMLFormElement::SubmitSubmission(HTMLFormSubmission* aFormSubmission)
730 {
731   nsresult rv;
732   nsIContent* originatingElement = aFormSubmission->GetOriginatingElement();
733 
734   //
735   // Get the action and target
736   //
737   nsCOMPtr<nsIURI> actionURI;
738   rv = GetActionURL(getter_AddRefs(actionURI), originatingElement);
739   NS_ENSURE_SUBMIT_SUCCESS(rv);
740 
741   if (!actionURI) {
742     mIsSubmitting = false;
743     return NS_OK;
744   }
745 
746   // If there is no link handler, then we won't actually be able to submit.
747   nsIDocument* doc = GetComposedDoc();
748   nsCOMPtr<nsISupports> container = doc ? doc->GetContainer() : nullptr;
749   nsCOMPtr<nsILinkHandler> linkHandler(do_QueryInterface(container));
750   if (!linkHandler || IsEditable()) {
751     mIsSubmitting = false;
752     return NS_OK;
753   }
754 
755   // javascript URIs are not really submissions; they just call a function.
756   // Also, they may synchronously call submit(), and we want them to be able to
757   // do so while still disallowing other double submissions. (Bug 139798)
758   // Note that any other URI types that are of equivalent type should also be
759   // added here.
760   // XXXbz this is a mess.  The real issue here is that nsJSChannel sets the
761   // LOAD_BACKGROUND flag, so doesn't notify us, compounded by the fact that
762   // the JS executes before we forget the submission in OnStateChange on
763   // STATE_STOP.  As a result, we have to make sure that we simply pretend
764   // we're not submitting when submitting to a JS URL.  That's kinda bogus, but
765   // there we are.
766   bool schemeIsJavaScript = false;
767   if (NS_SUCCEEDED(actionURI->SchemeIs("javascript", &schemeIsJavaScript)) &&
768       schemeIsJavaScript) {
769     mIsSubmitting = false;
770   }
771 
772   // The target is the originating element formtarget attribute if the element
773   // is a submit control and has such an attribute.
774   // Otherwise, the target is the form owner's target attribute,
775   // if it has such an attribute.
776   // Finally, if one of the child nodes of the head element is a base element
777   // with a target attribute, then the value of the target attribute of the
778   // first such base element; or, if there is no such element, the empty string.
779   nsAutoString target;
780   if (!(originatingElement && originatingElement->GetAttr(kNameSpaceID_None,
781                                                           nsGkAtoms::formtarget,
782                                                           target)) &&
783       !GetAttr(kNameSpaceID_None, nsGkAtoms::target, target)) {
784     GetBaseTarget(target);
785   }
786 
787   //
788   // Notify observers of submit
789   //
790   bool cancelSubmit = false;
791   if (mNotifiedObservers) {
792     cancelSubmit = mNotifiedObserversResult;
793   } else {
794     rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
795     NS_ENSURE_SUBMIT_SUCCESS(rv);
796   }
797 
798   if (cancelSubmit) {
799     mIsSubmitting = false;
800     return NS_OK;
801   }
802 
803   cancelSubmit = false;
804   rv = NotifySubmitObservers(actionURI, &cancelSubmit, false);
805   NS_ENSURE_SUBMIT_SUCCESS(rv);
806 
807   if (cancelSubmit) {
808     mIsSubmitting = false;
809     return NS_OK;
810   }
811 
812   //
813   // Submit
814   //
815   nsCOMPtr<nsIDocShell> docShell;
816 
817   {
818     nsAutoPopupStatePusher popupStatePusher(mSubmitPopupState);
819 
820     AutoHandlingUserInputStatePusher userInpStatePusher(
821                                        mSubmitInitiatedFromUserInput,
822                                        nullptr, doc);
823 
824     nsCOMPtr<nsIInputStream> postDataStream;
825     rv = aFormSubmission->GetEncodedSubmission(actionURI,
826                                                getter_AddRefs(postDataStream));
827     NS_ENSURE_SUBMIT_SUCCESS(rv);
828 
829     rv = linkHandler->OnLinkClickSync(this, actionURI,
830                                       target.get(),
831                                       NullString(),
832                                       postDataStream, nullptr,
833                                       getter_AddRefs(docShell),
834                                       getter_AddRefs(mSubmittingRequest));
835     NS_ENSURE_SUBMIT_SUCCESS(rv);
836   }
837 
838   // Even if the submit succeeds, it's possible for there to be no docshell
839   // or request; for example, if it's to a named anchor within the same page
840   // the submit will not really do anything.
841   if (docShell) {
842     // If the channel is pending, we have to listen for web progress.
843     bool pending = false;
844     mSubmittingRequest->IsPending(&pending);
845     if (pending && !schemeIsJavaScript) {
846       nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
847       NS_ASSERTION(webProgress, "nsIDocShell not converted to nsIWebProgress!");
848       rv = webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_ALL);
849       NS_ENSURE_SUBMIT_SUCCESS(rv);
850       mWebProgress = do_GetWeakReference(webProgress);
851       NS_ASSERTION(mWebProgress, "can't hold weak ref to webprogress!");
852     } else {
853       ForgetCurrentSubmission();
854     }
855   } else {
856     ForgetCurrentSubmission();
857   }
858 
859   return rv;
860 }
861 
862 nsresult
DoSecureToInsecureSubmitCheck(nsIURI * aActionURL,bool * aCancelSubmit)863 HTMLFormElement::DoSecureToInsecureSubmitCheck(nsIURI* aActionURL,
864                                                bool* aCancelSubmit)
865 {
866   *aCancelSubmit = false;
867 
868   // Only ask the user about posting from a secure URI to an insecure URI if
869   // this element is in the root document. When this is not the case, the mixed
870   // content blocker will take care of security for us.
871   nsIDocument* parent = OwnerDoc()->GetParentDocument();
872   bool isRootDocument = (!parent || nsContentUtils::IsChromeDoc(parent));
873   if (!isRootDocument) {
874     return NS_OK;
875   }
876 
877   nsIPrincipal* principal = NodePrincipal();
878   if (!principal) {
879     *aCancelSubmit = true;
880     return NS_OK;
881   }
882   nsCOMPtr<nsIURI> principalURI;
883   nsresult rv = principal->GetURI(getter_AddRefs(principalURI));
884   if (NS_FAILED(rv)) {
885     return rv;
886   }
887   if (!principalURI) {
888     principalURI = OwnerDoc()->GetDocumentURI();
889   }
890   bool formIsHTTPS;
891   rv = principalURI->SchemeIs("https", &formIsHTTPS);
892   if (NS_FAILED(rv)) {
893     return rv;
894   }
895   bool actionIsHTTPS;
896   rv = aActionURL->SchemeIs("https", &actionIsHTTPS);
897   if (NS_FAILED(rv)) {
898     return rv;
899   }
900   bool actionIsJS;
901   rv = aActionURL->SchemeIs("javascript", &actionIsJS);
902   if (NS_FAILED(rv)) {
903     return rv;
904   }
905 
906   if (!formIsHTTPS || actionIsHTTPS || actionIsJS) {
907     return NS_OK;
908   }
909 
910   nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
911   if (!window) {
912     return NS_ERROR_FAILURE;
913   }
914   nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
915   if (!docShell) {
916     return NS_ERROR_FAILURE;
917   }
918   nsCOMPtr<nsIPrompt> prompt = do_GetInterface(docShell);
919   if (!prompt) {
920     return NS_ERROR_FAILURE;
921   }
922   nsCOMPtr<nsIStringBundle> stringBundle;
923   nsCOMPtr<nsIStringBundleService> stringBundleService =
924     mozilla::services::GetStringBundleService();
925   if (!stringBundleService) {
926     return NS_ERROR_FAILURE;
927   }
928   rv = stringBundleService->CreateBundle(
929     "chrome://global/locale/browser.properties",
930     getter_AddRefs(stringBundle));
931   if (NS_FAILED(rv)) {
932     return rv;
933   }
934   nsAutoString title;
935   nsAutoString message;
936   nsAutoString cont;
937   stringBundle->GetStringFromName(
938     u"formPostSecureToInsecureWarning.title", getter_Copies(title));
939   stringBundle->GetStringFromName(
940     u"formPostSecureToInsecureWarning.message",
941     getter_Copies(message));
942   stringBundle->GetStringFromName(
943     u"formPostSecureToInsecureWarning.continue",
944     getter_Copies(cont));
945   int32_t buttonPressed;
946   bool checkState = false; // this is unused (ConfirmEx requires this parameter)
947   rv = prompt->ConfirmEx(title.get(), message.get(),
948                          (nsIPrompt::BUTTON_TITLE_IS_STRING *
949                           nsIPrompt::BUTTON_POS_0) +
950                          (nsIPrompt::BUTTON_TITLE_CANCEL *
951                           nsIPrompt::BUTTON_POS_1),
952                          cont.get(), nullptr, nullptr, nullptr,
953                             &checkState, &buttonPressed);
954   if (NS_FAILED(rv)) {
955     return rv;
956   }
957   *aCancelSubmit = (buttonPressed == 1);
958   uint32_t telemetryBucket =
959     nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE;
960   mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI,
961                                  telemetryBucket);
962   if (!*aCancelSubmit) {
963     // The user opted to continue, so note that in the next telemetry bucket.
964     mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI,
965                                    telemetryBucket + 1);
966   }
967   return NS_OK;
968 }
969 
970 nsresult
NotifySubmitObservers(nsIURI * aActionURL,bool * aCancelSubmit,bool aEarlyNotify)971 HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL,
972                                        bool* aCancelSubmit,
973                                        bool    aEarlyNotify)
974 {
975   // If this is the first form, bring alive the first form submit
976   // category observers
977   if (!gFirstFormSubmitted) {
978     gFirstFormSubmitted = true;
979     NS_CreateServicesFromCategory(NS_FIRST_FORMSUBMIT_CATEGORY,
980                                   nullptr,
981                                   NS_FIRST_FORMSUBMIT_CATEGORY);
982   }
983 
984   if (!aEarlyNotify) {
985     nsresult rv = DoSecureToInsecureSubmitCheck(aActionURL, aCancelSubmit);
986     if (NS_FAILED(rv)) {
987       return rv;
988     }
989     if (*aCancelSubmit) {
990       return NS_OK;
991     }
992   }
993 
994   // Notify observers that the form is being submitted.
995   nsCOMPtr<nsIObserverService> service =
996     mozilla::services::GetObserverService();
997   if (!service)
998     return NS_ERROR_FAILURE;
999 
1000   nsCOMPtr<nsISimpleEnumerator> theEnum;
1001   nsresult rv = service->EnumerateObservers(aEarlyNotify ?
1002                                             NS_EARLYFORMSUBMIT_SUBJECT :
1003                                             NS_FORMSUBMIT_SUBJECT,
1004                                             getter_AddRefs(theEnum));
1005   NS_ENSURE_SUCCESS(rv, rv);
1006 
1007   if (theEnum) {
1008     nsCOMPtr<nsISupports> inst;
1009     *aCancelSubmit = false;
1010 
1011     // XXXbz what do the submit observers actually want?  The window
1012     // of the document this is shown in?  Or something else?
1013     // sXBL/XBL2 issue
1014     nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
1015 
1016     bool loop = true;
1017     while (NS_SUCCEEDED(theEnum->HasMoreElements(&loop)) && loop) {
1018       theEnum->GetNext(getter_AddRefs(inst));
1019 
1020       nsCOMPtr<nsIFormSubmitObserver> formSubmitObserver(
1021                       do_QueryInterface(inst));
1022       if (formSubmitObserver) {
1023         rv = formSubmitObserver->Notify(this,
1024                                         window ? window->GetCurrentInnerWindow() : nullptr,
1025                                         aActionURL,
1026                                         aCancelSubmit);
1027         NS_ENSURE_SUCCESS(rv, rv);
1028       }
1029       if (*aCancelSubmit) {
1030         return NS_OK;
1031       }
1032     }
1033   }
1034 
1035   return rv;
1036 }
1037 
1038 
1039 nsresult
WalkFormElements(HTMLFormSubmission * aFormSubmission)1040 HTMLFormElement::WalkFormElements(HTMLFormSubmission* aFormSubmission)
1041 {
1042   nsTArray<nsGenericHTMLFormElement*> sortedControls;
1043   nsresult rv = mControls->GetSortedControls(sortedControls);
1044   NS_ENSURE_SUCCESS(rv, rv);
1045 
1046   uint32_t len = sortedControls.Length();
1047 
1048   // Hold a reference to the elements so they can't be deleted while
1049   // calling SubmitNamesValues().
1050   for (uint32_t i = 0; i < len; ++i) {
1051     static_cast<nsGenericHTMLElement*>(sortedControls[i])->AddRef();
1052   }
1053 
1054   //
1055   // Walk the list of nodes and call SubmitNamesValues() on the controls
1056   //
1057   for (uint32_t i = 0; i < len; ++i) {
1058     // Tell the control to submit its name/value pairs to the submission
1059     sortedControls[i]->SubmitNamesValues(aFormSubmission);
1060   }
1061 
1062   // Release the references.
1063   for (uint32_t i = 0; i < len; ++i) {
1064     static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
1065   }
1066 
1067   return NS_OK;
1068 }
1069 
1070 // nsIForm
1071 
NS_IMETHODIMP_(uint32_t)1072 NS_IMETHODIMP_(uint32_t)
1073 HTMLFormElement::GetElementCount() const
1074 {
1075   uint32_t count = 0;
1076   mControls->GetLength(&count);
1077   return count;
1078 }
1079 
1080 Element*
IndexedGetter(uint32_t aIndex,bool & aFound)1081 HTMLFormElement::IndexedGetter(uint32_t aIndex, bool &aFound)
1082 {
1083   Element* element = mControls->mElements.SafeElementAt(aIndex, nullptr);
1084   aFound = element != nullptr;
1085   return element;
1086 }
1087 
NS_IMETHODIMP_(nsIFormControl *)1088 NS_IMETHODIMP_(nsIFormControl*)
1089 HTMLFormElement::GetElementAt(int32_t aIndex) const
1090 {
1091   return mControls->mElements.SafeElementAt(aIndex, nullptr);
1092 }
1093 
1094 /**
1095  * Compares the position of aControl1 and aControl2 in the document
1096  * @param aControl1 First control to compare.
1097  * @param aControl2 Second control to compare.
1098  * @param aForm Parent form of the controls.
1099  * @return < 0 if aControl1 is before aControl2,
1100  *         > 0 if aControl1 is after aControl2,
1101  *         0 otherwise
1102  */
1103 /* static */ int32_t
CompareFormControlPosition(Element * aElement1,Element * aElement2,const nsIContent * aForm)1104 HTMLFormElement::CompareFormControlPosition(Element* aElement1,
1105                                             Element* aElement2,
1106                                             const nsIContent* aForm)
1107 {
1108   NS_ASSERTION(aElement1 != aElement2, "Comparing a form control to itself");
1109 
1110   // If an element has a @form, we can assume it *might* be able to not have
1111   // a parent and still be in the form.
1112   NS_ASSERTION((aElement1->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1113                 aElement1->GetParent()) &&
1114                (aElement2->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1115                 aElement2->GetParent()),
1116                "Form controls should always have parents");
1117 
1118   // If we pass aForm, we are assuming both controls are form descendants which
1119   // is not always the case. This function should work but maybe slower.
1120   // However, checking if both elements are form descendants may be slow too...
1121   // TODO: remove the prevent asserts fix, see bug 598468.
1122 #ifdef DEBUG
1123   nsLayoutUtils::gPreventAssertInCompareTreePosition = true;
1124   int32_t rVal = nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
1125   nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
1126 
1127   return rVal;
1128 #else // DEBUG
1129   return nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
1130 #endif // DEBUG
1131 }
1132 
1133 #ifdef DEBUG
1134 /**
1135  * Checks that all form elements are in document order. Asserts if any pair of
1136  * consecutive elements are not in increasing document order.
1137  *
1138  * @param aControls List of form controls to check.
1139  * @param aForm Parent form of the controls.
1140  */
1141 /* static */ void
AssertDocumentOrder(const nsTArray<nsGenericHTMLFormElement * > & aControls,nsIContent * aForm)1142 HTMLFormElement::AssertDocumentOrder(
1143   const nsTArray<nsGenericHTMLFormElement*>& aControls, nsIContent* aForm)
1144 {
1145   // TODO: remove the return statement with bug 598468.
1146   // This is done to prevent asserts in some edge cases.
1147   return;
1148 
1149   // Only iterate if aControls is not empty, since otherwise
1150   // |aControls.Length() - 1| will be a very large unsigned number... not what
1151   // we want here.
1152   if (!aControls.IsEmpty()) {
1153     for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
1154       NS_ASSERTION(CompareFormControlPosition(aControls[i], aControls[i + 1],
1155                                               aForm) < 0,
1156                    "Form controls not ordered correctly");
1157     }
1158   }
1159 }
1160 #endif
1161 
1162 void
PostPasswordEvent()1163 HTMLFormElement::PostPasswordEvent()
1164 {
1165   // Don't fire another add event if we have a pending add event.
1166   if (mFormPasswordEventDispatcher.get()) {
1167     return;
1168   }
1169 
1170   mFormPasswordEventDispatcher =
1171     new AsyncEventDispatcher(this, NS_LITERAL_STRING("DOMFormHasPassword"),
1172                              true, true);
1173   mFormPasswordEventDispatcher->PostDOMEvent();
1174 }
1175 
1176 namespace {
1177 
1178 struct FormComparator
1179 {
1180   Element* const mChild;
1181   HTMLFormElement* const mForm;
FormComparatormozilla::dom::__anon22f73a1d0111::FormComparator1182   FormComparator(Element* aChild, HTMLFormElement* aForm)
1183     : mChild(aChild), mForm(aForm) {}
operator ()mozilla::dom::__anon22f73a1d0111::FormComparator1184   int operator()(Element* aElement) const {
1185     return HTMLFormElement::CompareFormControlPosition(mChild, aElement, mForm);
1186   }
1187 };
1188 
1189 } // namespace
1190 
1191 // This function return true if the element, once appended, is the last one in
1192 // the array.
1193 template<typename ElementType>
1194 static bool
AddElementToList(nsTArray<ElementType * > & aList,ElementType * aChild,HTMLFormElement * aForm)1195 AddElementToList(nsTArray<ElementType*>& aList, ElementType* aChild,
1196                  HTMLFormElement* aForm)
1197 {
1198   NS_ASSERTION(aList.IndexOf(aChild) == aList.NoIndex,
1199                "aChild already in aList");
1200 
1201   const uint32_t count = aList.Length();
1202   ElementType* element;
1203   bool lastElement = false;
1204 
1205   // Optimize most common case where we insert at the end.
1206   int32_t position = -1;
1207   if (count > 0) {
1208     element = aList[count - 1];
1209     position =
1210       HTMLFormElement::CompareFormControlPosition(aChild, element, aForm);
1211   }
1212 
1213   // If this item comes after the last element, or the elements array is
1214   // empty, we append to the end. Otherwise, we do a binary search to
1215   // determine where the element should go.
1216   if (position >= 0 || count == 0) {
1217     // WEAK - don't addref
1218     aList.AppendElement(aChild);
1219     lastElement = true;
1220   }
1221   else {
1222     size_t idx;
1223     BinarySearchIf(aList, 0, count, FormComparator(aChild, aForm), &idx);
1224 
1225     // WEAK - don't addref
1226     aList.InsertElementAt(idx, aChild);
1227   }
1228 
1229   return lastElement;
1230 }
1231 
1232 nsresult
AddElement(nsGenericHTMLFormElement * aChild,bool aUpdateValidity,bool aNotify)1233 HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
1234                             bool aUpdateValidity, bool aNotify)
1235 {
1236   // If an element has a @form, we can assume it *might* be able to not have
1237   // a parent and still be in the form.
1238   NS_ASSERTION(aChild->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1239                aChild->GetParent(),
1240                "Form control should have a parent");
1241 
1242   // Determine whether to add the new element to the elements or
1243   // the not-in-elements list.
1244   bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(aChild);
1245   nsTArray<nsGenericHTMLFormElement*>& controlList = childInElements ?
1246       mControls->mElements : mControls->mNotInElements;
1247 
1248   bool lastElement = AddElementToList(controlList, aChild, this);
1249 
1250 #ifdef DEBUG
1251   AssertDocumentOrder(controlList, this);
1252 #endif
1253 
1254   int32_t type = aChild->GetType();
1255 
1256   //
1257   // If it is a password control, and the password manager has not yet been
1258   // initialized, initialize the password manager
1259   //
1260   if (type == NS_FORM_INPUT_PASSWORD) {
1261     if (!gPasswordManagerInitialized) {
1262       gPasswordManagerInitialized = true;
1263       NS_CreateServicesFromCategory(NS_PASSWORDMANAGER_CATEGORY,
1264                                     nullptr,
1265                                     NS_PASSWORDMANAGER_CATEGORY);
1266     }
1267     PostPasswordEvent();
1268   }
1269 
1270   // Default submit element handling
1271   if (aChild->IsSubmitControl()) {
1272     // Update mDefaultSubmitElement, mFirstSubmitInElements,
1273     // mFirstSubmitNotInElements.
1274 
1275     nsGenericHTMLFormElement** firstSubmitSlot =
1276       childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
1277 
1278     // The new child is the new first submit in its list if the firstSubmitSlot
1279     // is currently empty or if the child is before what's currently in the
1280     // slot.  Note that if we already have a control in firstSubmitSlot and
1281     // we're appending this element can't possibly replace what's currently in
1282     // the slot.  Also note that aChild can't become the mDefaultSubmitElement
1283     // unless it replaces what's in the slot.  If it _does_ replace what's in
1284     // the slot, it becomes the default submit if either the default submit is
1285     // what's in the slot or the child is earlier than the default submit.
1286     nsGenericHTMLFormElement* oldDefaultSubmit = mDefaultSubmitElement;
1287     if (!*firstSubmitSlot ||
1288         (!lastElement &&
1289          CompareFormControlPosition(aChild, *firstSubmitSlot, this) < 0)) {
1290       // Update mDefaultSubmitElement if it's currently in a valid state.
1291       // Valid state means either non-null or null because there are in fact
1292       // no submit elements around.
1293       if ((mDefaultSubmitElement ||
1294            (!mFirstSubmitInElements && !mFirstSubmitNotInElements)) &&
1295           (*firstSubmitSlot == mDefaultSubmitElement ||
1296            CompareFormControlPosition(aChild,
1297                                       mDefaultSubmitElement, this) < 0)) {
1298         mDefaultSubmitElement = aChild;
1299       }
1300       *firstSubmitSlot = aChild;
1301     }
1302     NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
1303                      mDefaultSubmitElement == mFirstSubmitNotInElements ||
1304                      !mDefaultSubmitElement,
1305                      "What happened here?");
1306 
1307     // Notify that the state of the previous default submit element has changed
1308     // if the element which is the default submit element has changed.  The new
1309     // default submit element is responsible for its own state update.
1310     if (oldDefaultSubmit && oldDefaultSubmit != mDefaultSubmitElement) {
1311       oldDefaultSubmit->UpdateState(aNotify);
1312     }
1313   }
1314 
1315   // If the element is subject to constraint validaton and is invalid, we need
1316   // to update our internal counter.
1317   if (aUpdateValidity) {
1318     nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
1319     if (cvElmt &&
1320         cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
1321       UpdateValidity(false);
1322     }
1323   }
1324 
1325   // Notify the radio button it's been added to a group
1326   // This has to be done _after_ UpdateValidity() call to prevent the element
1327   // being count twice.
1328   if (type == NS_FORM_INPUT_RADIO) {
1329     RefPtr<HTMLInputElement> radio =
1330       static_cast<HTMLInputElement*>(aChild);
1331     radio->AddedToRadioGroup();
1332   }
1333 
1334   return NS_OK;
1335 }
1336 
1337 nsresult
AddElementToTable(nsGenericHTMLFormElement * aChild,const nsAString & aName)1338 HTMLFormElement::AddElementToTable(nsGenericHTMLFormElement* aChild,
1339                                    const nsAString& aName)
1340 {
1341   return mControls->AddElementToTable(aChild, aName);
1342 }
1343 
1344 
1345 nsresult
RemoveElement(nsGenericHTMLFormElement * aChild,bool aUpdateValidity)1346 HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
1347                                bool aUpdateValidity)
1348 {
1349   //
1350   // Remove it from the radio group if it's a radio button
1351   //
1352   nsresult rv = NS_OK;
1353   if (aChild->GetType() == NS_FORM_INPUT_RADIO) {
1354     RefPtr<HTMLInputElement> radio =
1355       static_cast<HTMLInputElement*>(aChild);
1356     radio->WillRemoveFromRadioGroup();
1357   }
1358 
1359   // Determine whether to remove the child from the elements list
1360   // or the not in elements list.
1361   bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(aChild);
1362   nsTArray<nsGenericHTMLFormElement*>& controls = childInElements ?
1363       mControls->mElements :  mControls->mNotInElements;
1364 
1365   // Find the index of the child. This will be used later if necessary
1366   // to find the default submit.
1367   size_t index = controls.IndexOf(aChild);
1368   NS_ENSURE_STATE(index != controls.NoIndex);
1369 
1370   controls.RemoveElementAt(index);
1371 
1372   // Update our mFirstSubmit* values.
1373   nsGenericHTMLFormElement** firstSubmitSlot =
1374     childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
1375   if (aChild == *firstSubmitSlot) {
1376     *firstSubmitSlot = nullptr;
1377 
1378     // We are removing the first submit in this list, find the new first submit
1379     uint32_t length = controls.Length();
1380     for (uint32_t i = index; i < length; ++i) {
1381       nsGenericHTMLFormElement* currentControl = controls[i];
1382       if (currentControl->IsSubmitControl()) {
1383         *firstSubmitSlot = currentControl;
1384         break;
1385       }
1386     }
1387   }
1388 
1389   if (aChild == mDefaultSubmitElement) {
1390     // Need to reset mDefaultSubmitElement.  Do this asynchronously so
1391     // that we're not doing it while the DOM is in flux.
1392     mDefaultSubmitElement = nullptr;
1393     nsContentUtils::AddScriptRunner(new RemoveElementRunnable(this));
1394 
1395     // Note that we don't need to notify on the old default submit (which is
1396     // being removed) because it's either being removed from the DOM or
1397     // changing attributes in a way that makes it responsible for sending its
1398     // own notifications.
1399   }
1400 
1401   // If the element was subject to constraint validaton and is invalid, we need
1402   // to update our internal counter.
1403   if (aUpdateValidity) {
1404     nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
1405     if (cvElmt &&
1406         cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
1407       UpdateValidity(true);
1408     }
1409   }
1410 
1411   return rv;
1412 }
1413 
1414 void
HandleDefaultSubmitRemoval()1415 HTMLFormElement::HandleDefaultSubmitRemoval()
1416 {
1417   if (mDefaultSubmitElement) {
1418     // Already got reset somehow; nothing else to do here
1419     return;
1420   }
1421 
1422   if (!mFirstSubmitNotInElements) {
1423     mDefaultSubmitElement = mFirstSubmitInElements;
1424   } else if (!mFirstSubmitInElements) {
1425     mDefaultSubmitElement = mFirstSubmitNotInElements;
1426   } else {
1427     NS_ASSERTION(mFirstSubmitInElements != mFirstSubmitNotInElements,
1428                  "How did that happen?");
1429     // Have both; use the earlier one
1430     mDefaultSubmitElement =
1431       CompareFormControlPosition(mFirstSubmitInElements,
1432                                  mFirstSubmitNotInElements, this) < 0 ?
1433       mFirstSubmitInElements : mFirstSubmitNotInElements;
1434   }
1435 
1436   NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
1437                    mDefaultSubmitElement == mFirstSubmitNotInElements,
1438                    "What happened here?");
1439 
1440   // Notify about change if needed.
1441   if (mDefaultSubmitElement) {
1442     mDefaultSubmitElement->UpdateState(true);
1443   }
1444 }
1445 
1446 nsresult
RemoveElementFromTableInternal(nsInterfaceHashtable<nsStringHashKey,nsISupports> & aTable,nsIContent * aChild,const nsAString & aName)1447 HTMLFormElement::RemoveElementFromTableInternal(
1448   nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
1449   nsIContent* aChild, const nsAString& aName)
1450 {
1451   nsCOMPtr<nsISupports> supports;
1452 
1453   if (!aTable.Get(aName, getter_AddRefs(supports)))
1454     return NS_OK;
1455 
1456   // Single element in the hash, just remove it if it's the one
1457   // we're trying to remove...
1458   if (supports == aChild) {
1459     aTable.Remove(aName);
1460     ++mExpandoAndGeneration.generation;
1461     return NS_OK;
1462   }
1463 
1464   nsCOMPtr<nsIContent> content(do_QueryInterface(supports));
1465   if (content) {
1466     return NS_OK;
1467   }
1468 
1469   nsCOMPtr<nsIDOMNodeList> nodeList(do_QueryInterface(supports));
1470   NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
1471 
1472   // Upcast, uggly, but it works!
1473   nsBaseContentList *list = static_cast<nsBaseContentList*>(nodeList.get());
1474 
1475   list->RemoveElement(aChild);
1476 
1477   uint32_t length = 0;
1478   list->GetLength(&length);
1479 
1480   if (!length) {
1481     // If the list is empty we remove if from our hash, this shouldn't
1482     // happen tho
1483     aTable.Remove(aName);
1484     ++mExpandoAndGeneration.generation;
1485   } else if (length == 1) {
1486     // Only one element left, replace the list in the hash with the
1487     // single element.
1488     nsIContent* node = list->Item(0);
1489     if (node) {
1490       aTable.Put(aName, node);
1491     }
1492   }
1493 
1494   return NS_OK;
1495 }
1496 
1497 nsresult
RemoveElementFromTable(nsGenericHTMLFormElement * aElement,const nsAString & aName,RemoveElementReason aRemoveReason)1498 HTMLFormElement::RemoveElementFromTable(nsGenericHTMLFormElement* aElement,
1499                                         const nsAString& aName,
1500                                         RemoveElementReason aRemoveReason)
1501 {
1502   // If the element is being removed from the form, we have to remove it from
1503   // the past names map.
1504   if (aRemoveReason == ElementRemoved) {
1505     uint32_t oldCount = mPastNameLookupTable.Count();
1506     for (auto iter = mPastNameLookupTable.Iter(); !iter.Done(); iter.Next()) {
1507       if (static_cast<void*>(aElement) == iter.Data()) {
1508         iter.Remove();
1509       }
1510     }
1511     if (oldCount != mPastNameLookupTable.Count()) {
1512       ++mExpandoAndGeneration.generation;
1513     }
1514   }
1515 
1516   return mControls->RemoveElementFromTable(aElement, aName);
1517 }
1518 
1519 already_AddRefed<nsISupports>
NamedGetter(const nsAString & aName,bool & aFound)1520 HTMLFormElement::NamedGetter(const nsAString& aName, bool &aFound)
1521 {
1522   aFound = true;
1523 
1524   nsCOMPtr<nsISupports> result = DoResolveName(aName, true);
1525   if (result) {
1526     AddToPastNamesMap(aName, result);
1527     return result.forget();
1528   }
1529 
1530   result = mImageNameLookupTable.GetWeak(aName);
1531   if (result) {
1532     AddToPastNamesMap(aName, result);
1533     return result.forget();
1534   }
1535 
1536   result = mPastNameLookupTable.GetWeak(aName);
1537   if (result) {
1538     return result.forget();
1539   }
1540 
1541   aFound = false;
1542   return nullptr;
1543 }
1544 
1545 void
GetSupportedNames(nsTArray<nsString> & aRetval)1546 HTMLFormElement::GetSupportedNames(nsTArray<nsString >& aRetval)
1547 {
1548   // TODO https://github.com/whatwg/html/issues/1731
1549 }
1550 
1551 already_AddRefed<nsISupports>
FindNamedItem(const nsAString & aName,nsWrapperCache ** aCache)1552 HTMLFormElement::FindNamedItem(const nsAString& aName,
1553                                nsWrapperCache** aCache)
1554 {
1555   // FIXME Get the wrapper cache from DoResolveName.
1556 
1557   bool found;
1558   nsCOMPtr<nsISupports> result = NamedGetter(aName, found);
1559   if (result) {
1560     *aCache = nullptr;
1561     return result.forget();
1562   }
1563 
1564   return nullptr;
1565 }
1566 
1567 already_AddRefed<nsISupports>
DoResolveName(const nsAString & aName,bool aFlushContent)1568 HTMLFormElement::DoResolveName(const nsAString& aName,
1569                                bool aFlushContent)
1570 {
1571   nsCOMPtr<nsISupports> result =
1572     mControls->NamedItemInternal(aName, aFlushContent);
1573   return result.forget();
1574 }
1575 
1576 void
OnSubmitClickBegin(nsIContent * aOriginatingElement)1577 HTMLFormElement::OnSubmitClickBegin(nsIContent* aOriginatingElement)
1578 {
1579   mDeferSubmission = true;
1580 
1581   // Prepare to run NotifySubmitObservers early before the
1582   // scripts on the page get to modify the form data, possibly
1583   // throwing off any password manager. (bug 257781)
1584   nsCOMPtr<nsIURI> actionURI;
1585   nsresult rv;
1586 
1587   rv = GetActionURL(getter_AddRefs(actionURI), aOriginatingElement);
1588   if (NS_FAILED(rv) || !actionURI)
1589     return;
1590 
1591   // Notify observers of submit if the form is valid.
1592   // TODO: checking for mInvalidElementsCount is a temporary fix that should be
1593   // removed with bug 610402.
1594   if (mInvalidElementsCount == 0) {
1595     bool cancelSubmit = false;
1596     rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
1597     if (NS_SUCCEEDED(rv)) {
1598       mNotifiedObservers = true;
1599       mNotifiedObserversResult = cancelSubmit;
1600     }
1601   }
1602 }
1603 
1604 void
OnSubmitClickEnd()1605 HTMLFormElement::OnSubmitClickEnd()
1606 {
1607   mDeferSubmission = false;
1608 }
1609 
1610 void
FlushPendingSubmission()1611 HTMLFormElement::FlushPendingSubmission()
1612 {
1613   if (mPendingSubmission) {
1614     // Transfer owning reference so that the submissioin doesn't get deleted
1615     // if we reenter
1616     nsAutoPtr<HTMLFormSubmission> submission = Move(mPendingSubmission);
1617 
1618     SubmitSubmission(submission);
1619   }
1620 }
1621 
1622 nsresult
GetActionURL(nsIURI ** aActionURL,nsIContent * aOriginatingElement)1623 HTMLFormElement::GetActionURL(nsIURI** aActionURL,
1624                               nsIContent* aOriginatingElement)
1625 {
1626   nsresult rv = NS_OK;
1627 
1628   *aActionURL = nullptr;
1629 
1630   //
1631   // Grab the URL string
1632   //
1633   // If the originating element is a submit control and has the formaction
1634   // attribute specified, it should be used. Otherwise, the action attribute
1635   // from the form element should be used.
1636   //
1637   nsAutoString action;
1638 
1639   if (aOriginatingElement &&
1640       aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formaction)) {
1641 #ifdef DEBUG
1642     nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aOriginatingElement);
1643     NS_ASSERTION(formControl && formControl->IsSubmitControl(),
1644                  "The originating element must be a submit form control!");
1645 #endif // DEBUG
1646 
1647     nsCOMPtr<nsIDOMHTMLInputElement> inputElement = do_QueryInterface(aOriginatingElement);
1648     if (inputElement) {
1649       inputElement->GetFormAction(action);
1650     } else {
1651       nsCOMPtr<nsIDOMHTMLButtonElement> buttonElement = do_QueryInterface(aOriginatingElement);
1652       if (buttonElement) {
1653         buttonElement->GetFormAction(action);
1654       } else {
1655         NS_ERROR("Originating element must be an input or button element!");
1656         return NS_ERROR_UNEXPECTED;
1657       }
1658     }
1659   } else {
1660     GetAction(action);
1661   }
1662 
1663   //
1664   // Form the full action URL
1665   //
1666 
1667   // Get the document to form the URL.
1668   // We'll also need it later to get the DOM window when notifying form submit
1669   // observers (bug 33203)
1670   if (!IsInUncomposedDoc()) {
1671     return NS_OK; // No doc means don't submit, see Bug 28988
1672   }
1673 
1674   // Get base URL
1675   nsIDocument *document = OwnerDoc();
1676   nsIURI *docURI = document->GetDocumentURI();
1677   NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
1678 
1679   // If an action is not specified and we are inside
1680   // a HTML document then reload the URL. This makes us
1681   // compatible with 4.x browsers.
1682   // If we are in some other type of document such as XML or
1683   // XUL, do nothing. This prevents undesirable reloading of
1684   // a document inside XUL.
1685 
1686   nsCOMPtr<nsIURI> actionURL;
1687   if (action.IsEmpty()) {
1688     nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(document));
1689     if (!htmlDoc) {
1690       // Must be a XML, XUL or other non-HTML document type
1691       // so do nothing.
1692       return NS_OK;
1693     }
1694 
1695     rv = docURI->Clone(getter_AddRefs(actionURL));
1696     NS_ENSURE_SUCCESS(rv, rv);
1697   } else {
1698     nsCOMPtr<nsIURI> baseURL = GetBaseURI();
1699     NS_ASSERTION(baseURL, "No Base URL found in Form Submit!\n");
1700     if (!baseURL) {
1701       return NS_OK; // No base URL -> exit early, see Bug 30721
1702     }
1703     rv = NS_NewURI(getter_AddRefs(actionURL), action, nullptr, baseURL);
1704     NS_ENSURE_SUCCESS(rv, rv);
1705   }
1706 
1707   //
1708   // Verify the URL should be reached
1709   //
1710   // Get security manager, check to see if access to action URI is allowed.
1711   //
1712   nsIScriptSecurityManager *securityManager =
1713       nsContentUtils::GetSecurityManager();
1714   rv = securityManager->
1715     CheckLoadURIWithPrincipal(NodePrincipal(), actionURL,
1716                               nsIScriptSecurityManager::STANDARD);
1717   NS_ENSURE_SUCCESS(rv, rv);
1718 
1719   // Check if CSP allows this form-action
1720   nsCOMPtr<nsIContentSecurityPolicy> csp;
1721   rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
1722   NS_ENSURE_SUCCESS(rv, rv);
1723   if (csp) {
1724     bool permitsFormAction = true;
1725 
1726     // form-action is only enforced if explicitly defined in the
1727     // policy - do *not* consult default-src, see:
1728     // http://www.w3.org/TR/CSP2/#directive-default-src
1729     rv = csp->Permits(actionURL, nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
1730                       true, &permitsFormAction);
1731     NS_ENSURE_SUCCESS(rv, rv);
1732     if (!permitsFormAction) {
1733       return NS_ERROR_CSP_FORM_ACTION_VIOLATION;
1734     }
1735   }
1736 
1737   // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. In
1738   // such a case we have to upgrade the action url from http:// to https://.
1739   // If the actionURL is not http, then there is nothing to do.
1740   bool isHttpScheme = false;
1741   rv = actionURL->SchemeIs("http", &isHttpScheme);
1742   NS_ENSURE_SUCCESS(rv, rv);
1743   if (isHttpScheme && document->GetUpgradeInsecureRequests(false)) {
1744     // let's use the old specification before the upgrade for logging
1745     nsAutoCString spec;
1746     rv = actionURL->GetSpec(spec);
1747     NS_ENSURE_SUCCESS(rv, rv);
1748     NS_ConvertUTF8toUTF16 reportSpec(spec);
1749 
1750     // upgrade the actionURL from http:// to use https://
1751     nsCOMPtr<nsIURI> upgradedActionURL;
1752     rv = NS_GetSecureUpgradedURI(actionURL, getter_AddRefs(upgradedActionURL));
1753     NS_ENSURE_SUCCESS(rv, rv);
1754     actionURL = upgradedActionURL.forget();
1755 
1756     // let's log a message to the console that we are upgrading a request
1757     nsAutoCString scheme;
1758     rv = actionURL->GetScheme(scheme);
1759     NS_ENSURE_SUCCESS(rv, rv);
1760     NS_ConvertUTF8toUTF16 reportScheme(scheme);
1761 
1762     const char16_t* params[] = { reportSpec.get(), reportScheme.get() };
1763     CSP_LogLocalizedStr(u"upgradeInsecureRequest",
1764                         params, ArrayLength(params),
1765                         EmptyString(), // aSourceFile
1766                         EmptyString(), // aScriptSample
1767                         0, // aLineNumber
1768                         0, // aColumnNumber
1769                         nsIScriptError::warningFlag, "CSP",
1770                         document->InnerWindowID());
1771   }
1772 
1773   //
1774   // Assign to the output
1775   //
1776   actionURL.forget(aActionURL);
1777 
1778   return rv;
1779 }
1780 
NS_IMETHODIMP_(nsIFormControl *)1781 NS_IMETHODIMP_(nsIFormControl*)
1782 HTMLFormElement::GetDefaultSubmitElement() const
1783 {
1784   NS_PRECONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
1785                   mDefaultSubmitElement == mFirstSubmitNotInElements,
1786                   "What happened here?");
1787 
1788   return mDefaultSubmitElement;
1789 }
1790 
1791 bool
IsDefaultSubmitElement(const nsIFormControl * aControl) const1792 HTMLFormElement::IsDefaultSubmitElement(const nsIFormControl* aControl) const
1793 {
1794   NS_PRECONDITION(aControl, "Unexpected call");
1795 
1796   if (aControl == mDefaultSubmitElement) {
1797     // Yes, it is
1798     return true;
1799   }
1800 
1801   if (mDefaultSubmitElement ||
1802       (aControl != mFirstSubmitInElements &&
1803        aControl != mFirstSubmitNotInElements)) {
1804     // It isn't
1805     return false;
1806   }
1807 
1808   // mDefaultSubmitElement is null, but we have a non-null submit around
1809   // (aControl, in fact).  figure out whether it's in fact the default submit
1810   // and just hasn't been set that way yet.  Note that we can't just call
1811   // HandleDefaultSubmitRemoval because we might need to notify to handle that
1812   // correctly and we don't know whether that's safe right here.
1813   if (!mFirstSubmitInElements || !mFirstSubmitNotInElements) {
1814     // We only have one first submit; aControl has to be it
1815     return true;
1816   }
1817 
1818   // We have both kinds of submits.  Check which comes first.
1819   nsIFormControl* defaultSubmit =
1820     CompareFormControlPosition(mFirstSubmitInElements,
1821                                mFirstSubmitNotInElements, this) < 0 ?
1822       mFirstSubmitInElements : mFirstSubmitNotInElements;
1823   return aControl == defaultSubmit;
1824 }
1825 
1826 bool
ImplicitSubmissionIsDisabled() const1827 HTMLFormElement::ImplicitSubmissionIsDisabled() const
1828 {
1829   // Input text controls are always in the elements list.
1830   uint32_t numDisablingControlsFound = 0;
1831   uint32_t length = mControls->mElements.Length();
1832   for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
1833     if (mControls->mElements[i]->IsSingleLineTextControl(false) ||
1834         mControls->mElements[i]->GetType() == NS_FORM_INPUT_NUMBER) {
1835       numDisablingControlsFound++;
1836     }
1837   }
1838   return numDisablingControlsFound != 1;
1839 }
1840 
1841 NS_IMETHODIMP
GetEncoding(nsAString & aEncoding)1842 HTMLFormElement::GetEncoding(nsAString& aEncoding)
1843 {
1844   return GetEnctype(aEncoding);
1845 }
1846 
1847 NS_IMETHODIMP
SetEncoding(const nsAString & aEncoding)1848 HTMLFormElement::SetEncoding(const nsAString& aEncoding)
1849 {
1850   return SetEnctype(aEncoding);
1851 }
1852 
1853 int32_t
Length()1854 HTMLFormElement::Length()
1855 {
1856   return mControls->Length();
1857 }
1858 
1859 NS_IMETHODIMP
GetLength(int32_t * aLength)1860 HTMLFormElement::GetLength(int32_t* aLength)
1861 {
1862   *aLength = Length();
1863   return NS_OK;
1864 }
1865 
1866 void
ForgetCurrentSubmission()1867 HTMLFormElement::ForgetCurrentSubmission()
1868 {
1869   mNotifiedObservers = false;
1870   mIsSubmitting = false;
1871   mSubmittingRequest = nullptr;
1872   nsCOMPtr<nsIWebProgress> webProgress = do_QueryReferent(mWebProgress);
1873   if (webProgress) {
1874     webProgress->RemoveProgressListener(this);
1875   }
1876   mWebProgress = nullptr;
1877 }
1878 
1879 bool
CheckFormValidity(nsIMutableArray * aInvalidElements) const1880 HTMLFormElement::CheckFormValidity(nsIMutableArray* aInvalidElements) const
1881 {
1882   bool ret = true;
1883 
1884   nsTArray<nsGenericHTMLFormElement*> sortedControls;
1885   if (NS_FAILED(mControls->GetSortedControls(sortedControls))) {
1886     return false;
1887   }
1888 
1889   uint32_t len = sortedControls.Length();
1890 
1891   // Hold a reference to the elements so they can't be deleted while calling
1892   // the invalid events.
1893   for (uint32_t i = 0; i < len; ++i) {
1894     sortedControls[i]->AddRef();
1895   }
1896 
1897   for (uint32_t i = 0; i < len; ++i) {
1898     nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(sortedControls[i]);
1899     if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
1900         !cvElmt->IsValid()) {
1901       ret = false;
1902       bool defaultAction = true;
1903       nsContentUtils::DispatchTrustedEvent(sortedControls[i]->OwnerDoc(),
1904                                            static_cast<nsIContent*>(sortedControls[i]),
1905                                            NS_LITERAL_STRING("invalid"),
1906                                            false, true, &defaultAction);
1907 
1908       // Add all unhandled invalid controls to aInvalidElements if the caller
1909       // requested them.
1910       if (defaultAction && aInvalidElements) {
1911         aInvalidElements->AppendElement(ToSupports(sortedControls[i]),
1912                                         false);
1913       }
1914     }
1915   }
1916 
1917   // Release the references.
1918   for (uint32_t i = 0; i < len; ++i) {
1919     static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
1920   }
1921 
1922   return ret;
1923 }
1924 
1925 bool
CheckValidFormSubmission()1926 HTMLFormElement::CheckValidFormSubmission()
1927 {
1928   /**
1929    * Check for form validity: do not submit a form if there are unhandled
1930    * invalid controls in the form.
1931    * This should not be done if the form has been submitted with .submit().
1932    *
1933    * NOTE: for the moment, we are also checking that there is an observer for
1934    * NS_INVALIDFORMSUBMIT_SUBJECT so it will prevent blocking form submission
1935    * if the browser does not have implemented a UI yet.
1936    *
1937    * TODO: the check for observer should be removed later when HTML5 Forms will
1938    * be spread enough and authors will assume forms can't be submitted when
1939    * invalid. See bug 587671.
1940    */
1941 
1942   NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate),
1943                "We shouldn't be there if novalidate is set!");
1944 
1945   // When .submit() is called aEvent = nullptr so we can rely on that to know if
1946   // we have to check the validity of the form.
1947   nsCOMPtr<nsIObserverService> service =
1948     mozilla::services::GetObserverService();
1949   if (!service) {
1950     NS_WARNING("No observer service available!");
1951     return true;
1952   }
1953 
1954   nsCOMPtr<nsISimpleEnumerator> theEnum;
1955   nsresult rv = service->EnumerateObservers(NS_INVALIDFORMSUBMIT_SUBJECT,
1956                                             getter_AddRefs(theEnum));
1957   // Return true on error here because that's what we always did
1958   NS_ENSURE_SUCCESS(rv, true);
1959 
1960   bool hasObserver = false;
1961   rv = theEnum->HasMoreElements(&hasObserver);
1962 
1963   // Do not check form validity if there is no observer for
1964   // NS_INVALIDFORMSUBMIT_SUBJECT.
1965   if (NS_SUCCEEDED(rv) && hasObserver) {
1966     nsCOMPtr<nsIMutableArray> invalidElements =
1967       do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
1968     // Return true on error here because that's what we always did
1969     NS_ENSURE_SUCCESS(rv, true);
1970 
1971     if (!CheckFormValidity(invalidElements.get())) {
1972       // For the first invalid submission, we should update element states.
1973       // We have to do that _before_ calling the observers so we are sure they
1974       // will not interfere (like focusing the element).
1975       if (!mEverTriedInvalidSubmit) {
1976         mEverTriedInvalidSubmit = true;
1977 
1978         /*
1979          * We are going to call update states assuming elements want to
1980          * be notified because we can't know.
1981          * Submissions shouldn't happen during parsing so it _should_ be safe.
1982          */
1983 
1984         nsAutoScriptBlocker scriptBlocker;
1985 
1986         for (uint32_t i = 0, length = mControls->mElements.Length();
1987              i < length; ++i) {
1988           // Input elements can trigger a form submission and we want to
1989           // update the style in that case.
1990           if (mControls->mElements[i]->IsHTMLElement(nsGkAtoms::input) &&
1991               nsContentUtils::IsFocusedContent(mControls->mElements[i])) {
1992             static_cast<HTMLInputElement*>(mControls->mElements[i])
1993               ->UpdateValidityUIBits(true);
1994           }
1995 
1996           mControls->mElements[i]->UpdateState(true);
1997         }
1998 
1999         // Because of backward compatibility, <input type='image'> is not in
2000         // elements but can be invalid.
2001         // TODO: should probably be removed when bug 606491 will be fixed.
2002         for (uint32_t i = 0, length = mControls->mNotInElements.Length();
2003              i < length; ++i) {
2004           mControls->mNotInElements[i]->UpdateState(true);
2005         }
2006       }
2007 
2008       nsCOMPtr<nsISupports> inst;
2009       nsCOMPtr<nsIFormSubmitObserver> observer;
2010       bool more = true;
2011       while (NS_SUCCEEDED(theEnum->HasMoreElements(&more)) && more) {
2012         theEnum->GetNext(getter_AddRefs(inst));
2013         observer = do_QueryInterface(inst);
2014 
2015         if (observer) {
2016           observer->NotifyInvalidSubmit(this,
2017                                         static_cast<nsIArray*>(invalidElements));
2018         }
2019       }
2020 
2021       // The form is invalid. Observers have been alerted. Do not submit.
2022       return false;
2023     }
2024   } else {
2025     NS_WARNING("There is no observer for \"invalidformsubmit\". \
2026 One should be implemented!");
2027   }
2028 
2029   return true;
2030 }
2031 
2032 bool
SubmissionCanProceed(Element * aSubmitter)2033 HTMLFormElement::SubmissionCanProceed(Element* aSubmitter)
2034 {
2035 #ifdef DEBUG
2036   if (aSubmitter) {
2037     nsCOMPtr<nsIFormControl> fc = do_QueryInterface(aSubmitter);
2038     MOZ_ASSERT(fc);
2039 
2040     uint32_t type = fc->GetType();
2041     MOZ_ASSERT(type == NS_FORM_INPUT_SUBMIT ||
2042                type == NS_FORM_INPUT_IMAGE ||
2043                type == NS_FORM_BUTTON_SUBMIT,
2044                "aSubmitter is not a submit control?");
2045   }
2046 #endif
2047 
2048   // Modified step 2 of
2049   // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit --
2050   // we're not checking whether the node document is disconnected yet...
2051   if (OwnerDoc()->GetSandboxFlags() & SANDBOXED_FORMS) {
2052     return false;
2053   }
2054 
2055   if (HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
2056     return true;
2057   }
2058 
2059   if (aSubmitter &&
2060       aSubmitter->HasAttr(kNameSpaceID_None, nsGkAtoms::formnovalidate)) {
2061     return true;
2062   }
2063 
2064   return CheckValidFormSubmission();
2065 }
2066 
2067 void
UpdateValidity(bool aElementValidity)2068 HTMLFormElement::UpdateValidity(bool aElementValidity)
2069 {
2070   if (aElementValidity) {
2071     --mInvalidElementsCount;
2072   } else {
2073     ++mInvalidElementsCount;
2074   }
2075 
2076   NS_ASSERTION(mInvalidElementsCount >= 0, "Something went seriously wrong!");
2077 
2078   // The form validity has just changed if:
2079   // - there are no more invalid elements ;
2080   // - or there is one invalid elmement and an element just became invalid.
2081   // If we have invalid elements and we used to before as well, do nothing.
2082   if (mInvalidElementsCount &&
2083       (mInvalidElementsCount != 1 || aElementValidity)) {
2084     return;
2085   }
2086 
2087   /*
2088    * We are going to update states assuming submit controls want to
2089    * be notified because we can't know.
2090    * UpdateValidity shouldn't be called so much during parsing so it _should_
2091    * be safe.
2092    */
2093 
2094   nsAutoScriptBlocker scriptBlocker;
2095 
2096   // Inform submit controls that the form validity has changed.
2097   for (uint32_t i = 0, length = mControls->mElements.Length();
2098        i < length; ++i) {
2099     if (mControls->mElements[i]->IsSubmitControl()) {
2100       mControls->mElements[i]->UpdateState(true);
2101     }
2102   }
2103 
2104   // Because of backward compatibility, <input type='image'> is not in elements
2105   // so we have to check for controls not in elements too.
2106   uint32_t length = mControls->mNotInElements.Length();
2107   for (uint32_t i = 0; i < length; ++i) {
2108     if (mControls->mNotInElements[i]->IsSubmitControl()) {
2109       mControls->mNotInElements[i]->UpdateState(true);
2110     }
2111   }
2112 
2113   UpdateState(true);
2114 }
2115 
2116 // nsIWebProgressListener
2117 NS_IMETHODIMP
OnStateChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aStateFlags,nsresult aStatus)2118 HTMLFormElement::OnStateChange(nsIWebProgress* aWebProgress,
2119                                nsIRequest* aRequest,
2120                                uint32_t aStateFlags,
2121                                nsresult aStatus)
2122 {
2123   // If STATE_STOP is never fired for any reason (redirect?  Failed state
2124   // change?) the form element will leak.  It will be kept around by the
2125   // nsIWebProgressListener (assuming it keeps a strong pointer).  We will
2126   // consequently leak the request.
2127   if (aRequest == mSubmittingRequest &&
2128       aStateFlags & nsIWebProgressListener::STATE_STOP) {
2129     ForgetCurrentSubmission();
2130   }
2131 
2132   return NS_OK;
2133 }
2134 
2135 NS_IMETHODIMP
OnProgressChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,int32_t aCurSelfProgress,int32_t aMaxSelfProgress,int32_t aCurTotalProgress,int32_t aMaxTotalProgress)2136 HTMLFormElement::OnProgressChange(nsIWebProgress* aWebProgress,
2137                                   nsIRequest* aRequest,
2138                                   int32_t aCurSelfProgress,
2139                                   int32_t aMaxSelfProgress,
2140                                   int32_t aCurTotalProgress,
2141                                   int32_t aMaxTotalProgress)
2142 {
2143   NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2144   return NS_OK;
2145 }
2146 
2147 NS_IMETHODIMP
OnLocationChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsIURI * location,uint32_t aFlags)2148 HTMLFormElement::OnLocationChange(nsIWebProgress* aWebProgress,
2149                                   nsIRequest* aRequest,
2150                                   nsIURI* location,
2151                                   uint32_t aFlags)
2152 {
2153   NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2154   return NS_OK;
2155 }
2156 
2157 NS_IMETHODIMP
OnStatusChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsresult aStatus,const char16_t * aMessage)2158 HTMLFormElement::OnStatusChange(nsIWebProgress* aWebProgress,
2159                                 nsIRequest* aRequest,
2160                                 nsresult aStatus,
2161                                 const char16_t* aMessage)
2162 {
2163   NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2164   return NS_OK;
2165 }
2166 
2167 NS_IMETHODIMP
OnSecurityChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t state)2168 HTMLFormElement::OnSecurityChange(nsIWebProgress* aWebProgress,
2169                                   nsIRequest* aRequest,
2170                                   uint32_t state)
2171 {
2172   NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2173   return NS_OK;
2174 }
2175 
NS_IMETHODIMP_(int32_t)2176 NS_IMETHODIMP_(int32_t)
2177 HTMLFormElement::IndexOfControl(nsIFormControl* aControl)
2178 {
2179   int32_t index = 0;
2180   return mControls->IndexOfControl(aControl, &index) == NS_OK ? index : 0;
2181 }
2182 
2183 void
SetCurrentRadioButton(const nsAString & aName,HTMLInputElement * aRadio)2184 HTMLFormElement::SetCurrentRadioButton(const nsAString& aName,
2185                                        HTMLInputElement* aRadio)
2186 {
2187   mSelectedRadioButtons.Put(aName, aRadio);
2188 }
2189 
2190 HTMLInputElement*
GetCurrentRadioButton(const nsAString & aName)2191 HTMLFormElement::GetCurrentRadioButton(const nsAString& aName)
2192 {
2193   return mSelectedRadioButtons.GetWeak(aName);
2194 }
2195 
2196 NS_IMETHODIMP
GetNextRadioButton(const nsAString & aName,const bool aPrevious,HTMLInputElement * aFocusedRadio,HTMLInputElement ** aRadioOut)2197 HTMLFormElement::GetNextRadioButton(const nsAString& aName,
2198                                     const bool aPrevious,
2199                                     HTMLInputElement* aFocusedRadio,
2200                                     HTMLInputElement** aRadioOut)
2201 {
2202   // Return the radio button relative to the focused radio button.
2203   // If no radio is focused, get the radio relative to the selected one.
2204   *aRadioOut = nullptr;
2205 
2206   RefPtr<HTMLInputElement> currentRadio;
2207   if (aFocusedRadio) {
2208     currentRadio = aFocusedRadio;
2209   }
2210   else {
2211     mSelectedRadioButtons.Get(aName, getter_AddRefs(currentRadio));
2212   }
2213 
2214   nsCOMPtr<nsISupports> itemWithName = DoResolveName(aName, true);
2215   nsCOMPtr<nsINodeList> radioGroup(do_QueryInterface(itemWithName));
2216 
2217   if (!radioGroup) {
2218     return NS_ERROR_FAILURE;
2219   }
2220 
2221   int32_t index = radioGroup->IndexOf(currentRadio);
2222   if (index < 0) {
2223     return NS_ERROR_FAILURE;
2224   }
2225 
2226   uint32_t numRadios;
2227   radioGroup->GetLength(&numRadios);
2228   RefPtr<HTMLInputElement> radio;
2229 
2230   bool isRadio = false;
2231   do {
2232     if (aPrevious) {
2233       if (--index < 0) {
2234         index = numRadios -1;
2235       }
2236     }
2237     else if (++index >= (int32_t)numRadios) {
2238       index = 0;
2239     }
2240     radio = HTMLInputElement::FromContentOrNull(radioGroup->Item(index));
2241     isRadio = radio && radio->GetType() == NS_FORM_INPUT_RADIO;
2242     if (!isRadio) {
2243       continue;
2244     }
2245 
2246     nsAutoString name;
2247     radio->GetName(name);
2248     isRadio = aName.Equals(name);
2249   } while (!isRadio || (radio->Disabled() && radio != currentRadio));
2250 
2251   NS_IF_ADDREF(*aRadioOut = radio);
2252   return NS_OK;
2253 }
2254 
2255 NS_IMETHODIMP
WalkRadioGroup(const nsAString & aName,nsIRadioVisitor * aVisitor,bool aFlushContent)2256 HTMLFormElement::WalkRadioGroup(const nsAString& aName,
2257                                 nsIRadioVisitor* aVisitor,
2258                                 bool aFlushContent)
2259 {
2260   if (aName.IsEmpty()) {
2261     //
2262     // XXX If the name is empty, it's not stored in the control list.  There
2263     // *must* be a more efficient way to do this.
2264     //
2265     nsCOMPtr<nsIFormControl> control;
2266     uint32_t len = GetElementCount();
2267     for (uint32_t i = 0; i < len; i++) {
2268       control = GetElementAt(i);
2269       if (control->GetType() == NS_FORM_INPUT_RADIO) {
2270         nsCOMPtr<nsIContent> controlContent = do_QueryInterface(control);
2271         if (controlContent &&
2272             controlContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
2273                                         EmptyString(), eCaseMatters) &&
2274             !aVisitor->Visit(control)) {
2275           break;
2276         }
2277       }
2278     }
2279     return NS_OK;
2280   }
2281 
2282   // Get the control / list of controls from the form using form["name"]
2283   nsCOMPtr<nsISupports> item = DoResolveName(aName, aFlushContent);
2284   if (!item) {
2285     return NS_ERROR_FAILURE;
2286   }
2287 
2288   // If it's just a lone radio button, then select it.
2289   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(item);
2290   if (formControl) {
2291     if (formControl->GetType() == NS_FORM_INPUT_RADIO) {
2292       aVisitor->Visit(formControl);
2293     }
2294     return NS_OK;
2295   }
2296 
2297   nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(item);
2298   if (!nodeList) {
2299     return NS_OK;
2300   }
2301   uint32_t length = 0;
2302   nodeList->GetLength(&length);
2303   for (uint32_t i = 0; i < length; i++) {
2304     nsCOMPtr<nsIDOMNode> node;
2305     nodeList->Item(i, getter_AddRefs(node));
2306     nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(node);
2307     if (formControl && formControl->GetType() == NS_FORM_INPUT_RADIO &&
2308         !aVisitor->Visit(formControl)) {
2309       break;
2310     }
2311   }
2312   return NS_OK;
2313 }
2314 
2315 void
AddToRadioGroup(const nsAString & aName,nsIFormControl * aRadio)2316 HTMLFormElement::AddToRadioGroup(const nsAString& aName,
2317                                  nsIFormControl* aRadio)
2318 {
2319   nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
2320   NS_ASSERTION(element, "radio controls have to be content elements!");
2321 
2322   if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
2323     mRequiredRadioButtonCounts.Put(aName,
2324                                    mRequiredRadioButtonCounts.Get(aName)+1);
2325   }
2326 }
2327 
2328 void
RemoveFromRadioGroup(const nsAString & aName,nsIFormControl * aRadio)2329 HTMLFormElement::RemoveFromRadioGroup(const nsAString& aName,
2330                                       nsIFormControl* aRadio)
2331 {
2332   nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
2333   NS_ASSERTION(element, "radio controls have to be content elements!");
2334 
2335   if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
2336     uint32_t requiredNb = mRequiredRadioButtonCounts.Get(aName);
2337     NS_ASSERTION(requiredNb >= 1,
2338                  "At least one radio button has to be required!");
2339 
2340     if (requiredNb == 1) {
2341       mRequiredRadioButtonCounts.Remove(aName);
2342     } else {
2343       mRequiredRadioButtonCounts.Put(aName, requiredNb-1);
2344     }
2345   }
2346 }
2347 
2348 uint32_t
GetRequiredRadioCount(const nsAString & aName) const2349 HTMLFormElement::GetRequiredRadioCount(const nsAString& aName) const
2350 {
2351   return mRequiredRadioButtonCounts.Get(aName);
2352 }
2353 
2354 void
RadioRequiredWillChange(const nsAString & aName,bool aRequiredAdded)2355 HTMLFormElement::RadioRequiredWillChange(const nsAString& aName,
2356                                          bool aRequiredAdded)
2357 {
2358   if (aRequiredAdded) {
2359     mRequiredRadioButtonCounts.Put(aName,
2360                                    mRequiredRadioButtonCounts.Get(aName)+1);
2361   } else {
2362     uint32_t requiredNb = mRequiredRadioButtonCounts.Get(aName);
2363     NS_ASSERTION(requiredNb >= 1,
2364                  "At least one radio button has to be required!");
2365     if (requiredNb == 1) {
2366       mRequiredRadioButtonCounts.Remove(aName);
2367     } else {
2368       mRequiredRadioButtonCounts.Put(aName, requiredNb-1);
2369     }
2370   }
2371 }
2372 
2373 bool
GetValueMissingState(const nsAString & aName) const2374 HTMLFormElement::GetValueMissingState(const nsAString& aName) const
2375 {
2376   return mValueMissingRadioGroups.Get(aName);
2377 }
2378 
2379 void
SetValueMissingState(const nsAString & aName,bool aValue)2380 HTMLFormElement::SetValueMissingState(const nsAString& aName, bool aValue)
2381 {
2382   mValueMissingRadioGroups.Put(aName, aValue);
2383 }
2384 
2385 EventStates
IntrinsicState() const2386 HTMLFormElement::IntrinsicState() const
2387 {
2388   EventStates state = nsGenericHTMLElement::IntrinsicState();
2389 
2390   if (mInvalidElementsCount) {
2391     state |= NS_EVENT_STATE_INVALID;
2392   } else {
2393       state |= NS_EVENT_STATE_VALID;
2394   }
2395 
2396   return state;
2397 }
2398 
2399 void
Clear()2400 HTMLFormElement::Clear()
2401 {
2402   for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
2403     mImageElements[i]->ClearForm(false);
2404   }
2405   mImageElements.Clear();
2406   mImageNameLookupTable.Clear();
2407   mPastNameLookupTable.Clear();
2408 }
2409 
2410 namespace {
2411 
2412 struct PositionComparator
2413 {
2414   nsIContent* const mElement;
PositionComparatormozilla::dom::__anon22f73a1d0211::PositionComparator2415   explicit PositionComparator(nsIContent* const aElement) : mElement(aElement) {}
2416 
operator ()mozilla::dom::__anon22f73a1d0211::PositionComparator2417   int operator()(nsIContent* aElement) const {
2418     if (mElement == aElement) {
2419       return 0;
2420     }
2421     if (nsContentUtils::PositionIsBefore(mElement, aElement)) {
2422       return -1;
2423     }
2424     return 1;
2425   }
2426 };
2427 
2428 struct NodeListAdaptor
2429 {
2430   nsINodeList* const mList;
NodeListAdaptormozilla::dom::__anon22f73a1d0211::NodeListAdaptor2431   explicit NodeListAdaptor(nsINodeList* aList) : mList(aList) {}
operator []mozilla::dom::__anon22f73a1d0211::NodeListAdaptor2432   nsIContent* operator[](size_t aIdx) const {
2433     return mList->Item(aIdx);
2434   }
2435 };
2436 
2437 } // namespace
2438 
2439 nsresult
AddElementToTableInternal(nsInterfaceHashtable<nsStringHashKey,nsISupports> & aTable,nsIContent * aChild,const nsAString & aName)2440 HTMLFormElement::AddElementToTableInternal(
2441   nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
2442   nsIContent* aChild, const nsAString& aName)
2443 {
2444   nsCOMPtr<nsISupports> supports;
2445   aTable.Get(aName, getter_AddRefs(supports));
2446 
2447   if (!supports) {
2448     // No entry found, add the element
2449     aTable.Put(aName, aChild);
2450     ++mExpandoAndGeneration.generation;
2451   } else {
2452     // Found something in the hash, check its type
2453     nsCOMPtr<nsIContent> content = do_QueryInterface(supports);
2454 
2455     if (content) {
2456       // Check if the new content is the same as the one we found in the
2457       // hash, if it is then we leave it in the hash as it is, this will
2458       // happen if a form control has both a name and an id with the same
2459       // value
2460       if (content == aChild) {
2461         return NS_OK;
2462       }
2463 
2464       // Found an element, create a list, add the element to the list and put
2465       // the list in the hash
2466       RadioNodeList *list = new RadioNodeList(this);
2467 
2468       // If an element has a @form, we can assume it *might* be able to not have
2469       // a parent and still be in the form.
2470       NS_ASSERTION(content->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
2471                    content->GetParent(), "Item in list without parent");
2472 
2473       // Determine the ordering between the new and old element.
2474       bool newFirst = nsContentUtils::PositionIsBefore(aChild, content);
2475 
2476       list->AppendElement(newFirst ? aChild : content.get());
2477       list->AppendElement(newFirst ? content.get() : aChild);
2478 
2479 
2480       nsCOMPtr<nsISupports> listSupports = do_QueryObject(list);
2481 
2482       // Replace the element with the list.
2483       aTable.Put(aName, listSupports);
2484     } else {
2485       // There's already a list in the hash, add the child to the list
2486       nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports);
2487       NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
2488 
2489       // Upcast, uggly, but it works!
2490       RadioNodeList *list =
2491         static_cast<RadioNodeList*>(nodeList.get());
2492 
2493       NS_ASSERTION(list->Length() > 1,
2494                    "List should have been converted back to a single element");
2495 
2496       // Fast-path appends; this check is ok even if the child is
2497       // already in the list, since if it tests true the child would
2498       // have come at the end of the list, and the PositionIsBefore
2499       // will test false.
2500       if (nsContentUtils::PositionIsBefore(list->Item(list->Length() - 1), aChild)) {
2501         list->AppendElement(aChild);
2502         return NS_OK;
2503       }
2504 
2505       // If a control has a name equal to its id, it could be in the
2506       // list already.
2507       if (list->IndexOf(aChild) != -1) {
2508         return NS_OK;
2509       }
2510 
2511       size_t idx;
2512       DebugOnly<bool> found = BinarySearchIf(NodeListAdaptor(list), 0, list->Length(),
2513                                              PositionComparator(aChild), &idx);
2514       MOZ_ASSERT(!found, "should not have found an element");
2515 
2516       list->InsertElementAt(aChild, idx);
2517     }
2518   }
2519 
2520   return NS_OK;
2521 }
2522 
2523 nsresult
AddImageElement(HTMLImageElement * aChild)2524 HTMLFormElement::AddImageElement(HTMLImageElement* aChild)
2525 {
2526   AddElementToList(mImageElements, aChild, this);
2527   return NS_OK;
2528 }
2529 
2530 nsresult
AddImageElementToTable(HTMLImageElement * aChild,const nsAString & aName)2531 HTMLFormElement::AddImageElementToTable(HTMLImageElement* aChild,
2532                                         const nsAString& aName)
2533 {
2534   return AddElementToTableInternal(mImageNameLookupTable, aChild, aName);
2535 }
2536 
2537 nsresult
RemoveImageElement(HTMLImageElement * aChild)2538 HTMLFormElement::RemoveImageElement(HTMLImageElement* aChild)
2539 {
2540   size_t index = mImageElements.IndexOf(aChild);
2541   NS_ENSURE_STATE(index != mImageElements.NoIndex);
2542 
2543   mImageElements.RemoveElementAt(index);
2544   return NS_OK;
2545 }
2546 
2547 nsresult
RemoveImageElementFromTable(HTMLImageElement * aElement,const nsAString & aName,RemoveElementReason aRemoveReason)2548 HTMLFormElement::RemoveImageElementFromTable(HTMLImageElement* aElement,
2549                                              const nsAString& aName,
2550                                              RemoveElementReason aRemoveReason)
2551 {
2552   // If the element is being removed from the form, we have to remove it from
2553   // the past names map.
2554   if (aRemoveReason == ElementRemoved) {
2555     for (auto iter = mPastNameLookupTable.Iter(); !iter.Done(); iter.Next()) {
2556       if (static_cast<void*>(aElement) == iter.Data()) {
2557         iter.Remove();
2558       }
2559     }
2560   }
2561 
2562   return RemoveElementFromTableInternal(mImageNameLookupTable, aElement, aName);
2563 }
2564 
2565 void
AddToPastNamesMap(const nsAString & aName,nsISupports * aChild)2566 HTMLFormElement::AddToPastNamesMap(const nsAString& aName,
2567                                    nsISupports* aChild)
2568 {
2569   // If candidates contains exactly one node. Add a mapping from name to the
2570   // node in candidates in the form element's past names map, replacing the
2571   // previous entry with the same name, if any.
2572   nsCOMPtr<nsIContent> node = do_QueryInterface(aChild);
2573   if (node) {
2574     mPastNameLookupTable.Put(aName, aChild);
2575   }
2576 }
2577 
2578 JSObject*
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)2579 HTMLFormElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
2580 {
2581   return HTMLFormElementBinding::Wrap(aCx, this, aGivenProto);
2582 }
2583 
2584 } // namespace dom
2585 } // namespace mozilla
2586