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