Bumping gaia.json for 8 gaia revision(s) a=gaia-bump
[gecko.git] / dom / html / HTMLFormElement.cpp
blob378a7f7e56e26d692d0ec4a9038badd7f9ab5d74
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/HTMLFormElement.h"
8 #include "jsapi.h"
9 #include "mozilla/ContentEvents.h"
10 #include "mozilla/EventDispatcher.h"
11 #include "mozilla/EventStateManager.h"
12 #include "mozilla/EventStates.h"
13 #include "mozilla/dom/AutocompleteErrorEvent.h"
14 #include "mozilla/dom/HTMLFormControlsCollection.h"
15 #include "mozilla/dom/HTMLFormElementBinding.h"
16 #include "mozilla/Move.h"
17 #include "nsIHTMLDocument.h"
18 #include "nsGkAtoms.h"
19 #include "nsStyleConsts.h"
20 #include "nsPresContext.h"
21 #include "nsIDocument.h"
22 #include "nsIFormControlFrame.h"
23 #include "nsISecureBrowserUI.h"
24 #include "nsError.h"
25 #include "nsContentUtils.h"
26 #include "nsInterfaceHashtable.h"
27 #include "nsContentList.h"
28 #include "nsCOMArray.h"
29 #include "nsAutoPtr.h"
30 #include "nsTArray.h"
31 #include "nsIMutableArray.h"
32 #include "nsIFormAutofillContentService.h"
33 #include "mozilla/BinarySearch.h"
35 // form submission
36 #include "nsIFormSubmitObserver.h"
37 #include "nsIObserverService.h"
38 #include "nsICategoryManager.h"
39 #include "nsCategoryManagerUtils.h"
40 #include "nsISimpleEnumerator.h"
41 #include "nsRange.h"
42 #include "nsIScriptSecurityManager.h"
43 #include "nsNetUtil.h"
44 #include "nsIWebProgress.h"
45 #include "nsIDocShell.h"
46 #include "nsFormData.h"
47 #include "nsFormSubmissionConstants.h"
49 // radio buttons
50 #include "mozilla/dom/HTMLInputElement.h"
51 #include "nsIRadioVisitor.h"
52 #include "RadioNodeList.h"
54 #include "nsLayoutUtils.h"
56 #include "mozAutoDocUpdate.h"
57 #include "nsIHTMLCollection.h"
59 #include "nsIConstraintValidation.h"
61 #include "nsIDOMHTMLButtonElement.h"
62 #include "nsSandboxFlags.h"
64 #include "nsIContentSecurityPolicy.h"
66 // images
67 #include "mozilla/dom/HTMLImageElement.h"
69 // construction, destruction
70 NS_IMPL_NS_NEW_HTML_ELEMENT(Form)
72 namespace mozilla {
73 namespace dom {
75 static const uint8_t NS_FORM_AUTOCOMPLETE_ON = 1;
76 static const uint8_t NS_FORM_AUTOCOMPLETE_OFF = 0;
78 static const nsAttrValue::EnumTable kFormAutocompleteTable[] = {
79 { "on", NS_FORM_AUTOCOMPLETE_ON },
80 { "off", NS_FORM_AUTOCOMPLETE_OFF },
81 { 0 }
83 // Default autocomplete value is 'on'.
84 static const nsAttrValue::EnumTable* kFormDefaultAutocomplete = &kFormAutocompleteTable[0];
86 bool HTMLFormElement::gFirstFormSubmitted = false;
87 bool HTMLFormElement::gPasswordManagerInitialized = false;
89 HTMLFormElement::HTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
90 : nsGenericHTMLElement(aNodeInfo),
91 mControls(new HTMLFormControlsCollection(this)),
92 mSelectedRadioButtons(2),
93 mRequiredRadioButtonCounts(2),
94 mValueMissingRadioGroups(2),
95 mGeneratingSubmit(false),
96 mGeneratingReset(false),
97 mIsSubmitting(false),
98 mDeferSubmission(false),
99 mNotifiedObservers(false),
100 mNotifiedObserversResult(false),
101 mSubmitPopupState(openAbused),
102 mSubmitInitiatedFromUserInput(false),
103 mPendingSubmission(nullptr),
104 mSubmittingRequest(nullptr),
105 mDefaultSubmitElement(nullptr),
106 mFirstSubmitInElements(nullptr),
107 mFirstSubmitNotInElements(nullptr),
108 mImageNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
109 mPastNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
110 mInvalidElementsCount(0),
111 mEverTriedInvalidSubmit(false)
115 HTMLFormElement::~HTMLFormElement()
117 if (mControls) {
118 mControls->DropFormReference();
121 Clear();
124 // nsISupports
126 static PLDHashOperator
127 ElementTraverser(const nsAString& key, HTMLInputElement* element,
128 void* userArg)
130 nsCycleCollectionTraversalCallback *cb =
131 static_cast<nsCycleCollectionTraversalCallback*>(userArg);
133 cb->NoteXPCOMChild(ToSupports(element));
134 return PL_DHASH_NEXT;
137 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormElement)
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 tmp->mSelectedRadioButtons.EnumerateRead(ElementTraverser, &cb);
145 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
147 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
148 nsGenericHTMLElement)
149 tmp->Clear();
150 tmp->mExpandoAndGeneration.Unlink();
151 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
153 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(HTMLFormElement,
154 nsGenericHTMLElement)
155 if (tmp->PreservingWrapper()) {
156 NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mExpandoAndGeneration.expando);
158 NS_IMPL_CYCLE_COLLECTION_TRACE_END
160 NS_IMPL_ADDREF_INHERITED(HTMLFormElement, Element)
161 NS_IMPL_RELEASE_INHERITED(HTMLFormElement, Element)
164 // QueryInterface implementation for HTMLFormElement
165 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLFormElement)
166 NS_INTERFACE_TABLE_INHERITED(HTMLFormElement,
167 nsIDOMHTMLFormElement,
168 nsIForm,
169 nsIWebProgressListener,
170 nsIRadioGroupContainer)
171 NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
174 // nsIDOMHTMLFormElement
176 NS_IMPL_ELEMENT_CLONE(HTMLFormElement)
178 nsIHTMLCollection*
179 HTMLFormElement::Elements()
181 return mControls;
184 NS_IMETHODIMP
185 HTMLFormElement::GetElements(nsIDOMHTMLCollection** aElements)
187 *aElements = Elements();
188 NS_ADDREF(*aElements);
189 return NS_OK;
192 nsresult
193 HTMLFormElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
194 nsIAtom* aPrefix, const nsAString& aValue,
195 bool aNotify)
197 if ((aName == nsGkAtoms::action || aName == nsGkAtoms::target) &&
198 aNameSpaceID == kNameSpaceID_None) {
199 if (mPendingSubmission) {
200 // aha, there is a pending submission that means we're in
201 // the script and we need to flush it. let's tell it
202 // that the event was ignored to force the flush.
203 // the second argument is not playing a role at all.
204 FlushPendingSubmission();
206 // Don't forget we've notified the password manager already if the
207 // page sets the action/target in the during submit. (bug 343182)
208 bool notifiedObservers = mNotifiedObservers;
209 ForgetCurrentSubmission();
210 mNotifiedObservers = notifiedObservers;
212 return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
213 aNotify);
216 nsresult
217 HTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
218 const nsAttrValue* aValue, bool aNotify)
220 if (aName == nsGkAtoms::novalidate && aNameSpaceID == kNameSpaceID_None) {
221 // Update all form elements states because they might be [no longer]
222 // affected by :-moz-ui-valid or :-moz-ui-invalid.
223 for (uint32_t i = 0, length = mControls->mElements.Length();
224 i < length; ++i) {
225 mControls->mElements[i]->UpdateState(true);
228 for (uint32_t i = 0, length = mControls->mNotInElements.Length();
229 i < length; ++i) {
230 mControls->mNotInElements[i]->UpdateState(true);
234 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify);
237 NS_IMPL_STRING_ATTR(HTMLFormElement, AcceptCharset, acceptcharset)
238 NS_IMPL_ACTION_ATTR(HTMLFormElement, Action, action)
239 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Autocomplete, autocomplete,
240 kFormDefaultAutocomplete->tag)
241 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Enctype, enctype,
242 kFormDefaultEnctype->tag)
243 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Method, method,
244 kFormDefaultMethod->tag)
245 NS_IMPL_BOOL_ATTR(HTMLFormElement, NoValidate, novalidate)
246 NS_IMPL_STRING_ATTR(HTMLFormElement, Name, name)
247 NS_IMPL_STRING_ATTR(HTMLFormElement, Target, target)
249 void
250 HTMLFormElement::Submit(ErrorResult& aRv)
252 // Send the submit event
253 if (mPendingSubmission) {
254 // aha, we have a pending submission that was not flushed
255 // (this happens when form.submit() is called twice)
256 // we have to delete it and build a new one since values
257 // might have changed inbetween (we emulate IE here, that's all)
258 mPendingSubmission = nullptr;
261 aRv = DoSubmitOrReset(nullptr, NS_FORM_SUBMIT);
264 NS_IMETHODIMP
265 HTMLFormElement::Submit()
267 ErrorResult rv;
268 Submit(rv);
269 return rv.ErrorCode();
272 NS_IMETHODIMP
273 HTMLFormElement::Reset()
275 InternalFormEvent event(true, NS_FORM_RESET);
276 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), nullptr, &event);
277 return NS_OK;
280 NS_IMETHODIMP
281 HTMLFormElement::CheckValidity(bool* retVal)
283 *retVal = CheckValidity();
284 return NS_OK;
287 void
288 HTMLFormElement::RequestAutocomplete()
290 bool dummy;
291 nsCOMPtr<nsIDOMWindow> window =
292 do_QueryInterface(OwnerDoc()->GetScriptHandlingObject(dummy));
293 nsCOMPtr<nsIFormAutofillContentService> formAutofillContentService =
294 do_GetService("@mozilla.org/formautofill/content-service;1");
296 if (!formAutofillContentService || !window) {
297 AutocompleteErrorEventInit init;
298 init.mBubbles = true;
299 init.mCancelable = false;
300 init.mReason = AutoCompleteErrorReason::Disabled;
302 nsRefPtr<AutocompleteErrorEvent> event =
303 AutocompleteErrorEvent::Constructor(this, NS_LITERAL_STRING("autocompleteerror"), init);
305 (new AsyncEventDispatcher(this, event))->PostDOMEvent();
306 return;
309 formAutofillContentService->RequestAutocomplete(this, window);
312 bool
313 HTMLFormElement::ParseAttribute(int32_t aNamespaceID,
314 nsIAtom* aAttribute,
315 const nsAString& aValue,
316 nsAttrValue& aResult)
318 if (aNamespaceID == kNameSpaceID_None) {
319 if (aAttribute == nsGkAtoms::method) {
320 return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
322 if (aAttribute == nsGkAtoms::enctype) {
323 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
325 if (aAttribute == nsGkAtoms::autocomplete) {
326 return aResult.ParseEnumValue(aValue, kFormAutocompleteTable, false);
330 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
331 aResult);
334 nsresult
335 HTMLFormElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
336 nsIContent* aBindingParent,
337 bool aCompileEventHandlers)
339 nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
340 aBindingParent,
341 aCompileEventHandlers);
342 NS_ENSURE_SUCCESS(rv, rv);
344 nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(aDocument));
345 if (htmlDoc) {
346 htmlDoc->AddedForm();
349 return rv;
352 template<typename T>
353 static void
354 MarkOrphans(const nsTArray<T*>& aArray)
356 uint32_t length = aArray.Length();
357 for (uint32_t i = 0; i < length; ++i) {
358 aArray[i]->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
362 static void
363 CollectOrphans(nsINode* aRemovalRoot,
364 const nsTArray<nsGenericHTMLFormElement*>& aArray
365 #ifdef DEBUG
366 , nsIDOMHTMLFormElement* aThisForm
367 #endif
370 // Put a script blocker around all the notifications we're about to do.
371 nsAutoScriptBlocker scriptBlocker;
373 // Walk backwards so that if we remove elements we can just keep iterating
374 uint32_t length = aArray.Length();
375 for (uint32_t i = length; i > 0; --i) {
376 nsGenericHTMLFormElement* node = aArray[i-1];
378 // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
379 // node is in fact a descendant of the form and hence should stay in the
380 // form. If it _is_ set, then we need to check whether the node is a
381 // descendant of aRemovalRoot. If it is, we leave it in the form.
382 #ifdef DEBUG
383 bool removed = false;
384 #endif
385 if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
386 node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
387 if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
388 node->ClearForm(true);
390 // When a form control loses its form owner, its state can change.
391 node->UpdateState(true);
392 #ifdef DEBUG
393 removed = true;
394 #endif
398 #ifdef DEBUG
399 if (!removed) {
400 nsCOMPtr<nsIDOMHTMLFormElement> form;
401 node->GetForm(getter_AddRefs(form));
402 NS_ASSERTION(form == aThisForm, "How did that happen?");
404 #endif /* DEBUG */
408 static void
409 CollectOrphans(nsINode* aRemovalRoot,
410 const nsTArray<HTMLImageElement*>& aArray
411 #ifdef DEBUG
412 , nsIDOMHTMLFormElement* aThisForm
413 #endif
416 // Walk backwards so that if we remove elements we can just keep iterating
417 uint32_t length = aArray.Length();
418 for (uint32_t i = length; i > 0; --i) {
419 HTMLImageElement* node = aArray[i-1];
421 // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
422 // node is in fact a descendant of the form and hence should stay in the
423 // form. If it _is_ set, then we need to check whether the node is a
424 // descendant of aRemovalRoot. If it is, we leave it in the form.
425 #ifdef DEBUG
426 bool removed = false;
427 #endif
428 if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
429 node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
430 if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
431 node->ClearForm(true);
433 #ifdef DEBUG
434 removed = true;
435 #endif
439 #ifdef DEBUG
440 if (!removed) {
441 nsCOMPtr<nsIDOMHTMLFormElement> form = node->GetForm();
442 NS_ASSERTION(form == aThisForm, "How did that happen?");
444 #endif /* DEBUG */
448 void
449 HTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent)
451 nsCOMPtr<nsIHTMLDocument> oldDocument = do_QueryInterface(GetUncomposedDoc());
453 // Mark all of our controls as maybe being orphans
454 MarkOrphans(mControls->mElements);
455 MarkOrphans(mControls->mNotInElements);
456 MarkOrphans(mImageElements);
458 nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
460 nsINode* ancestor = this;
461 nsINode* cur;
462 do {
463 cur = ancestor->GetParentNode();
464 if (!cur) {
465 break;
467 ancestor = cur;
468 } while (1);
470 CollectOrphans(ancestor, mControls->mElements
471 #ifdef DEBUG
472 , this
473 #endif
475 CollectOrphans(ancestor, mControls->mNotInElements
476 #ifdef DEBUG
477 , this
478 #endif
480 CollectOrphans(ancestor, mImageElements
481 #ifdef DEBUG
482 , this
483 #endif
486 if (oldDocument) {
487 oldDocument->RemovedForm();
489 ForgetCurrentSubmission();
492 nsresult
493 HTMLFormElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
495 aVisitor.mWantsWillHandleEvent = true;
496 if (aVisitor.mEvent->originalTarget == static_cast<nsIContent*>(this)) {
497 uint32_t msg = aVisitor.mEvent->message;
498 if (msg == NS_FORM_SUBMIT) {
499 if (mGeneratingSubmit) {
500 aVisitor.mCanHandle = false;
501 return NS_OK;
503 mGeneratingSubmit = true;
505 // let the form know that it needs to defer the submission,
506 // that means that if there are scripted submissions, the
507 // latest one will be deferred until after the exit point of the handler.
508 mDeferSubmission = true;
510 else if (msg == NS_FORM_RESET) {
511 if (mGeneratingReset) {
512 aVisitor.mCanHandle = false;
513 return NS_OK;
515 mGeneratingReset = true;
518 return nsGenericHTMLElement::PreHandleEvent(aVisitor);
521 nsresult
522 HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor)
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->message == NS_FORM_SUBMIT ||
528 aVisitor.mEvent->message == NS_FORM_RESET) &&
529 aVisitor.mEvent->mFlags.mInBubblingPhase &&
530 aVisitor.mEvent->originalTarget != static_cast<nsIContent*>(this)) {
531 aVisitor.mEvent->mFlags.mPropagationStopped = true;
533 return NS_OK;
536 nsresult
537 HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
539 if (aVisitor.mEvent->originalTarget == static_cast<nsIContent*>(this)) {
540 uint32_t msg = aVisitor.mEvent->message;
541 if (msg == NS_FORM_SUBMIT) {
542 // let the form know not to defer subsequent submissions
543 mDeferSubmission = false;
546 if (aVisitor.mEventStatus == nsEventStatus_eIgnore) {
547 switch (msg) {
548 case NS_FORM_RESET:
549 case NS_FORM_SUBMIT:
551 if (mPendingSubmission && msg == NS_FORM_SUBMIT) {
552 // tell the form to forget a possible pending submission.
553 // the reason is that the script returned true (the event was
554 // ignored) so if there is a stored submission, it will miss
555 // the name/value of the submitting element, thus we need
556 // to forget it and the form element will build a new one
557 mPendingSubmission = nullptr;
559 DoSubmitOrReset(aVisitor.mEvent, msg);
561 break;
563 } else {
564 if (msg == NS_FORM_SUBMIT) {
565 // tell the form to flush a possible pending submission.
566 // the reason is that the script returned false (the event was
567 // not ignored) so if there is a stored submission, it needs to
568 // be submitted immediatelly.
569 FlushPendingSubmission();
573 if (msg == NS_FORM_SUBMIT) {
574 mGeneratingSubmit = false;
576 else if (msg == NS_FORM_RESET) {
577 mGeneratingReset = false;
580 return NS_OK;
583 nsresult
584 HTMLFormElement::DoSubmitOrReset(WidgetEvent* aEvent,
585 int32_t aMessage)
587 // Make sure the presentation is up-to-date
588 nsIDocument* doc = GetComposedDoc();
589 if (doc) {
590 doc->FlushPendingNotifications(Flush_ContentAndNotify);
593 // JBK Don't get form frames anymore - bug 34297
595 // Submit or Reset the form
596 if (NS_FORM_RESET == aMessage) {
597 return DoReset();
600 if (NS_FORM_SUBMIT == 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;
606 return DoSubmit(aEvent);
609 MOZ_ASSERT(false);
610 return NS_OK;
613 nsresult
614 HTMLFormElement::DoReset()
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();
626 return NS_OK;
629 #define NS_ENSURE_SUBMIT_SUCCESS(rv) \
630 if (NS_FAILED(rv)) { \
631 ForgetCurrentSubmission(); \
632 return rv; \
635 nsresult
636 HTMLFormElement::DoSubmit(WidgetEvent* aEvent)
638 NS_ASSERTION(GetComposedDoc(), "Should never get here without a current doc");
640 if (mIsSubmitting) {
641 NS_WARNING("Preventing double form submission");
642 // XXX Should this return an error?
643 return NS_OK;
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!");
650 nsAutoPtr<nsFormSubmission> submission;
653 // prepare the submission object
655 nsresult rv = BuildSubmission(getter_Transfers(submission), aEvent);
656 if (NS_FAILED(rv)) {
657 mIsSubmitting = false;
658 return rv;
661 // XXXbz if the script global is that for an sXBL/XBL2 doc, it won't
662 // be a window...
663 nsPIDOMWindow *window = OwnerDoc()->GetWindow();
665 if (window) {
666 mSubmitPopupState = window->GetPopupControlState();
667 } else {
668 mSubmitPopupState = openAbused;
671 mSubmitInitiatedFromUserInput = EventStateManager::IsHandlingUserInput();
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;
684 // perform the submission
686 return SubmitSubmission(submission);
689 nsresult
690 HTMLFormElement::BuildSubmission(nsFormSubmission** aFormSubmission,
691 WidgetEvent* aEvent)
693 NS_ASSERTION(!mPendingSubmission, "tried to build two submissions!");
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->originator;
701 if (originator) {
702 if (!originator->IsHTML()) {
703 return NS_ERROR_UNEXPECTED;
705 originatingElement = static_cast<nsGenericHTMLElement*>(originator);
710 nsresult rv;
713 // Get the submission object
715 rv = GetSubmissionFromForm(this, originatingElement, aFormSubmission);
716 NS_ENSURE_SUBMIT_SUCCESS(rv);
719 // Dump the data into the submission object
721 rv = WalkFormElements(*aFormSubmission);
722 NS_ENSURE_SUBMIT_SUCCESS(rv);
724 return NS_OK;
727 nsresult
728 HTMLFormElement::SubmitSubmission(nsFormSubmission* aFormSubmission)
730 nsresult rv;
731 nsIContent* originatingElement = aFormSubmission->GetOriginatingElement();
734 // Get the action and target
736 nsCOMPtr<nsIURI> actionURI;
737 rv = GetActionURL(getter_AddRefs(actionURI), originatingElement);
738 NS_ENSURE_SUBMIT_SUCCESS(rv);
740 if (!actionURI) {
741 mIsSubmitting = false;
742 return NS_OK;
745 // If there is no link handler, then we won't actually be able to submit.
746 nsIDocument* doc = GetComposedDoc();
747 nsCOMPtr<nsISupports> container = doc ? doc->GetContainer() : nullptr;
748 nsCOMPtr<nsILinkHandler> linkHandler(do_QueryInterface(container));
749 if (!linkHandler || IsEditable()) {
750 mIsSubmitting = false;
751 return NS_OK;
754 // javascript URIs are not really submissions; they just call a function.
755 // Also, they may synchronously call submit(), and we want them to be able to
756 // do so while still disallowing other double submissions. (Bug 139798)
757 // Note that any other URI types that are of equivalent type should also be
758 // added here.
759 // XXXbz this is a mess. The real issue here is that nsJSChannel sets the
760 // LOAD_BACKGROUND flag, so doesn't notify us, compounded by the fact that
761 // the JS executes before we forget the submission in OnStateChange on
762 // STATE_STOP. As a result, we have to make sure that we simply pretend
763 // we're not submitting when submitting to a JS URL. That's kinda bogus, but
764 // there we are.
765 bool schemeIsJavaScript = false;
766 if (NS_SUCCEEDED(actionURI->SchemeIs("javascript", &schemeIsJavaScript)) &&
767 schemeIsJavaScript) {
768 mIsSubmitting = false;
771 // The target is the originating element formtarget attribute if the element
772 // is a submit control and has such an attribute.
773 // Otherwise, the target is the form owner's target attribute,
774 // if it has such an attribute.
775 // Finally, if one of the child nodes of the head element is a base element
776 // with a target attribute, then the value of the target attribute of the
777 // first such base element; or, if there is no such element, the empty string.
778 nsAutoString target;
779 if (!(originatingElement && originatingElement->GetAttr(kNameSpaceID_None,
780 nsGkAtoms::formtarget,
781 target)) &&
782 !GetAttr(kNameSpaceID_None, nsGkAtoms::target, target)) {
783 GetBaseTarget(target);
787 // Notify observers of submit
789 bool cancelSubmit = false;
790 if (mNotifiedObservers) {
791 cancelSubmit = mNotifiedObserversResult;
792 } else {
793 rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
794 NS_ENSURE_SUBMIT_SUCCESS(rv);
797 if (cancelSubmit) {
798 mIsSubmitting = false;
799 return NS_OK;
802 cancelSubmit = false;
803 rv = NotifySubmitObservers(actionURI, &cancelSubmit, false);
804 NS_ENSURE_SUBMIT_SUCCESS(rv);
806 if (cancelSubmit) {
807 mIsSubmitting = false;
808 return NS_OK;
812 // Submit
814 nsCOMPtr<nsIDocShell> docShell;
817 nsAutoPopupStatePusher popupStatePusher(mSubmitPopupState);
819 AutoHandlingUserInputStatePusher userInpStatePusher(
820 mSubmitInitiatedFromUserInput,
821 nullptr, doc);
823 nsCOMPtr<nsIInputStream> postDataStream;
824 rv = aFormSubmission->GetEncodedSubmission(actionURI,
825 getter_AddRefs(postDataStream));
826 NS_ENSURE_SUBMIT_SUCCESS(rv);
828 rv = linkHandler->OnLinkClickSync(this, actionURI,
829 target.get(),
830 NullString(),
831 postDataStream, nullptr,
832 getter_AddRefs(docShell),
833 getter_AddRefs(mSubmittingRequest));
834 NS_ENSURE_SUBMIT_SUCCESS(rv);
837 // Even if the submit succeeds, it's possible for there to be no docshell
838 // or request; for example, if it's to a named anchor within the same page
839 // the submit will not really do anything.
840 if (docShell) {
841 // If the channel is pending, we have to listen for web progress.
842 bool pending = false;
843 mSubmittingRequest->IsPending(&pending);
844 if (pending && !schemeIsJavaScript) {
845 nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
846 NS_ASSERTION(webProgress, "nsIDocShell not converted to nsIWebProgress!");
847 rv = webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_ALL);
848 NS_ENSURE_SUBMIT_SUCCESS(rv);
849 mWebProgress = do_GetWeakReference(webProgress);
850 NS_ASSERTION(mWebProgress, "can't hold weak ref to webprogress!");
851 } else {
852 ForgetCurrentSubmission();
854 } else {
855 ForgetCurrentSubmission();
858 return rv;
861 nsresult
862 HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL,
863 bool* aCancelSubmit,
864 bool aEarlyNotify)
866 // If this is the first form, bring alive the first form submit
867 // category observers
868 if (!gFirstFormSubmitted) {
869 gFirstFormSubmitted = true;
870 NS_CreateServicesFromCategory(NS_FIRST_FORMSUBMIT_CATEGORY,
871 nullptr,
872 NS_FIRST_FORMSUBMIT_CATEGORY);
875 // XXXbz what do the submit observers actually want? The window
876 // of the document this is shown in? Or something else?
877 // sXBL/XBL2 issue
878 nsCOMPtr<nsPIDOMWindow> window = OwnerDoc()->GetWindow();
880 // Notify the secure browser UI, if any, that the form is being submitted.
881 nsCOMPtr<nsIDocShell> docshell = OwnerDoc()->GetDocShell();
882 if (docshell && !aEarlyNotify) {
883 nsCOMPtr<nsISecureBrowserUI> secureUI;
884 docshell->GetSecurityUI(getter_AddRefs(secureUI));
885 nsCOMPtr<nsIFormSubmitObserver> formSubmitObserver =
886 do_QueryInterface(secureUI);
887 if (formSubmitObserver) {
888 nsresult rv = formSubmitObserver->Notify(this,
889 window,
890 aActionURL,
891 aCancelSubmit);
892 NS_ENSURE_SUCCESS(rv, rv);
894 if (*aCancelSubmit) {
895 return NS_OK;
900 // Notify observers that the form is being submitted.
901 nsCOMPtr<nsIObserverService> service =
902 mozilla::services::GetObserverService();
903 if (!service)
904 return NS_ERROR_FAILURE;
906 nsCOMPtr<nsISimpleEnumerator> theEnum;
907 nsresult rv = service->EnumerateObservers(aEarlyNotify ?
908 NS_EARLYFORMSUBMIT_SUBJECT :
909 NS_FORMSUBMIT_SUBJECT,
910 getter_AddRefs(theEnum));
911 NS_ENSURE_SUCCESS(rv, rv);
913 if (theEnum) {
914 nsCOMPtr<nsISupports> inst;
915 *aCancelSubmit = false;
917 bool loop = true;
918 while (NS_SUCCEEDED(theEnum->HasMoreElements(&loop)) && loop) {
919 theEnum->GetNext(getter_AddRefs(inst));
921 nsCOMPtr<nsIFormSubmitObserver> formSubmitObserver(
922 do_QueryInterface(inst));
923 if (formSubmitObserver) {
924 rv = formSubmitObserver->Notify(this,
925 window,
926 aActionURL,
927 aCancelSubmit);
928 NS_ENSURE_SUCCESS(rv, rv);
930 if (*aCancelSubmit) {
931 return NS_OK;
936 return rv;
940 nsresult
941 HTMLFormElement::WalkFormElements(nsFormSubmission* aFormSubmission)
943 nsTArray<nsGenericHTMLFormElement*> sortedControls;
944 nsresult rv = mControls->GetSortedControls(sortedControls);
945 NS_ENSURE_SUCCESS(rv, rv);
947 uint32_t len = sortedControls.Length();
949 // Hold a reference to the elements so they can't be deleted while
950 // calling SubmitNamesValues().
951 for (uint32_t i = 0; i < len; ++i) {
952 static_cast<nsGenericHTMLElement*>(sortedControls[i])->AddRef();
956 // Walk the list of nodes and call SubmitNamesValues() on the controls
958 for (uint32_t i = 0; i < len; ++i) {
959 // Tell the control to submit its name/value pairs to the submission
960 sortedControls[i]->SubmitNamesValues(aFormSubmission);
963 // Release the references.
964 for (uint32_t i = 0; i < len; ++i) {
965 static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
968 return NS_OK;
971 // nsIForm
973 NS_IMETHODIMP_(uint32_t)
974 HTMLFormElement::GetElementCount() const
976 uint32_t count = 0;
977 mControls->GetLength(&count);
978 return count;
981 Element*
982 HTMLFormElement::IndexedGetter(uint32_t aIndex, bool &aFound)
984 Element* element = mControls->mElements.SafeElementAt(aIndex, nullptr);
985 aFound = element != nullptr;
986 return element;
989 NS_IMETHODIMP_(nsIFormControl*)
990 HTMLFormElement::GetElementAt(int32_t aIndex) const
992 return mControls->mElements.SafeElementAt(aIndex, nullptr);
996 * Compares the position of aControl1 and aControl2 in the document
997 * @param aControl1 First control to compare.
998 * @param aControl2 Second control to compare.
999 * @param aForm Parent form of the controls.
1000 * @return < 0 if aControl1 is before aControl2,
1001 * > 0 if aControl1 is after aControl2,
1002 * 0 otherwise
1004 /* static */ int32_t
1005 HTMLFormElement::CompareFormControlPosition(Element* aElement1,
1006 Element* aElement2,
1007 const nsIContent* aForm)
1009 NS_ASSERTION(aElement1 != aElement2, "Comparing a form control to itself");
1011 // If an element has a @form, we can assume it *might* be able to not have
1012 // a parent and still be in the form.
1013 NS_ASSERTION((aElement1->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1014 aElement1->GetParent()) &&
1015 (aElement2->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1016 aElement2->GetParent()),
1017 "Form controls should always have parents");
1019 // If we pass aForm, we are assuming both controls are form descendants which
1020 // is not always the case. This function should work but maybe slower.
1021 // However, checking if both elements are form descendants may be slow too...
1022 // TODO: remove the prevent asserts fix, see bug 598468.
1023 #ifdef DEBUG
1024 nsLayoutUtils::gPreventAssertInCompareTreePosition = true;
1025 int32_t rVal = nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
1026 nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
1028 return rVal;
1029 #else // DEBUG
1030 return nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
1031 #endif // DEBUG
1034 #ifdef DEBUG
1036 * Checks that all form elements are in document order. Asserts if any pair of
1037 * consecutive elements are not in increasing document order.
1039 * @param aControls List of form controls to check.
1040 * @param aForm Parent form of the controls.
1042 /* static */ void
1043 HTMLFormElement::AssertDocumentOrder(
1044 const nsTArray<nsGenericHTMLFormElement*>& aControls, nsIContent* aForm)
1046 // TODO: remove the return statement with bug 598468.
1047 // This is done to prevent asserts in some edge cases.
1048 return;
1050 // Only iterate if aControls is not empty, since otherwise
1051 // |aControls.Length() - 1| will be a very large unsigned number... not what
1052 // we want here.
1053 if (!aControls.IsEmpty()) {
1054 for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
1055 NS_ASSERTION(CompareFormControlPosition(aControls[i], aControls[i + 1],
1056 aForm) < 0,
1057 "Form controls not ordered correctly");
1061 #endif
1063 void
1064 HTMLFormElement::PostPasswordEvent()
1066 // Don't fire another add event if we have a pending add event.
1067 if (mFormPasswordEventDispatcher.get()) {
1068 return;
1071 mFormPasswordEventDispatcher =
1072 new FormPasswordEventDispatcher(this,
1073 NS_LITERAL_STRING("DOMFormHasPassword"));
1074 mFormPasswordEventDispatcher->PostDOMEvent();
1077 namespace {
1079 struct FormComparator
1081 Element* const mChild;
1082 HTMLFormElement* const mForm;
1083 FormComparator(Element* aChild, HTMLFormElement* aForm)
1084 : mChild(aChild), mForm(aForm) {}
1085 int operator()(Element* aElement) const {
1086 return HTMLFormElement::CompareFormControlPosition(mChild, aElement, mForm);
1090 } // namespace
1092 // This function return true if the element, once appended, is the last one in
1093 // the array.
1094 template<typename ElementType>
1095 static bool
1096 AddElementToList(nsTArray<ElementType*>& aList, ElementType* aChild,
1097 HTMLFormElement* aForm)
1099 NS_ASSERTION(aList.IndexOf(aChild) == aList.NoIndex,
1100 "aChild already in aList");
1102 const uint32_t count = aList.Length();
1103 ElementType* element;
1104 bool lastElement = false;
1106 // Optimize most common case where we insert at the end.
1107 int32_t position = -1;
1108 if (count > 0) {
1109 element = aList[count - 1];
1110 position =
1111 HTMLFormElement::CompareFormControlPosition(aChild, element, aForm);
1114 // If this item comes after the last element, or the elements array is
1115 // empty, we append to the end. Otherwise, we do a binary search to
1116 // determine where the element should go.
1117 if (position >= 0 || count == 0) {
1118 // WEAK - don't addref
1119 aList.AppendElement(aChild);
1120 lastElement = true;
1122 else {
1123 size_t idx;
1124 BinarySearchIf(aList, 0, count, FormComparator(aChild, aForm), &idx);
1126 // WEAK - don't addref
1127 aList.InsertElementAt(idx, aChild);
1130 return lastElement;
1133 nsresult
1134 HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
1135 bool aUpdateValidity, bool aNotify)
1137 // If an element has a @form, we can assume it *might* be able to not have
1138 // a parent and still be in the form.
1139 NS_ASSERTION(aChild->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1140 aChild->GetParent(),
1141 "Form control should have a parent");
1143 // Determine whether to add the new element to the elements or
1144 // the not-in-elements list.
1145 bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(aChild);
1146 nsTArray<nsGenericHTMLFormElement*>& controlList = childInElements ?
1147 mControls->mElements : mControls->mNotInElements;
1149 bool lastElement = AddElementToList(controlList, aChild, this);
1151 #ifdef DEBUG
1152 AssertDocumentOrder(controlList, this);
1153 #endif
1155 int32_t type = aChild->GetType();
1158 // If it is a password control, and the password manager has not yet been
1159 // initialized, initialize the password manager
1161 if (type == NS_FORM_INPUT_PASSWORD) {
1162 if (!gPasswordManagerInitialized) {
1163 gPasswordManagerInitialized = true;
1164 NS_CreateServicesFromCategory(NS_PASSWORDMANAGER_CATEGORY,
1165 nullptr,
1166 NS_PASSWORDMANAGER_CATEGORY);
1168 PostPasswordEvent();
1171 // Default submit element handling
1172 if (aChild->IsSubmitControl()) {
1173 // Update mDefaultSubmitElement, mFirstSubmitInElements,
1174 // mFirstSubmitNotInElements.
1176 nsGenericHTMLFormElement** firstSubmitSlot =
1177 childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
1179 // The new child is the new first submit in its list if the firstSubmitSlot
1180 // is currently empty or if the child is before what's currently in the
1181 // slot. Note that if we already have a control in firstSubmitSlot and
1182 // we're appending this element can't possibly replace what's currently in
1183 // the slot. Also note that aChild can't become the mDefaultSubmitElement
1184 // unless it replaces what's in the slot. If it _does_ replace what's in
1185 // the slot, it becomes the default submit if either the default submit is
1186 // what's in the slot or the child is earlier than the default submit.
1187 nsGenericHTMLFormElement* oldDefaultSubmit = mDefaultSubmitElement;
1188 if (!*firstSubmitSlot ||
1189 (!lastElement &&
1190 CompareFormControlPosition(aChild, *firstSubmitSlot, this) < 0)) {
1191 // Update mDefaultSubmitElement if it's currently in a valid state.
1192 // Valid state means either non-null or null because there are in fact
1193 // no submit elements around.
1194 if ((mDefaultSubmitElement ||
1195 (!mFirstSubmitInElements && !mFirstSubmitNotInElements)) &&
1196 (*firstSubmitSlot == mDefaultSubmitElement ||
1197 CompareFormControlPosition(aChild,
1198 mDefaultSubmitElement, this) < 0)) {
1199 mDefaultSubmitElement = aChild;
1201 *firstSubmitSlot = aChild;
1203 NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
1204 mDefaultSubmitElement == mFirstSubmitNotInElements ||
1205 !mDefaultSubmitElement,
1206 "What happened here?");
1208 // Notify that the state of the previous default submit element has changed
1209 // if the element which is the default submit element has changed. The new
1210 // default submit element is responsible for its own state update.
1211 if (oldDefaultSubmit && oldDefaultSubmit != mDefaultSubmitElement) {
1212 oldDefaultSubmit->UpdateState(aNotify);
1216 // If the element is subject to constraint validaton and is invalid, we need
1217 // to update our internal counter.
1218 if (aUpdateValidity) {
1219 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
1220 if (cvElmt &&
1221 cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
1222 UpdateValidity(false);
1226 // Notify the radio button it's been added to a group
1227 // This has to be done _after_ UpdateValidity() call to prevent the element
1228 // being count twice.
1229 if (type == NS_FORM_INPUT_RADIO) {
1230 nsRefPtr<HTMLInputElement> radio =
1231 static_cast<HTMLInputElement*>(aChild);
1232 radio->AddedToRadioGroup();
1235 return NS_OK;
1238 nsresult
1239 HTMLFormElement::AddElementToTable(nsGenericHTMLFormElement* aChild,
1240 const nsAString& aName)
1242 return mControls->AddElementToTable(aChild, aName);
1246 nsresult
1247 HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
1248 bool aUpdateValidity)
1251 // Remove it from the radio group if it's a radio button
1253 nsresult rv = NS_OK;
1254 if (aChild->GetType() == NS_FORM_INPUT_RADIO) {
1255 nsRefPtr<HTMLInputElement> radio =
1256 static_cast<HTMLInputElement*>(aChild);
1257 radio->WillRemoveFromRadioGroup();
1260 // Determine whether to remove the child from the elements list
1261 // or the not in elements list.
1262 bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(aChild);
1263 nsTArray<nsGenericHTMLFormElement*>& controls = childInElements ?
1264 mControls->mElements : mControls->mNotInElements;
1266 // Find the index of the child. This will be used later if necessary
1267 // to find the default submit.
1268 size_t index = controls.IndexOf(aChild);
1269 NS_ENSURE_STATE(index != controls.NoIndex);
1271 controls.RemoveElementAt(index);
1273 // Update our mFirstSubmit* values.
1274 nsGenericHTMLFormElement** firstSubmitSlot =
1275 childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
1276 if (aChild == *firstSubmitSlot) {
1277 *firstSubmitSlot = nullptr;
1279 // We are removing the first submit in this list, find the new first submit
1280 uint32_t length = controls.Length();
1281 for (uint32_t i = index; i < length; ++i) {
1282 nsGenericHTMLFormElement* currentControl = controls[i];
1283 if (currentControl->IsSubmitControl()) {
1284 *firstSubmitSlot = currentControl;
1285 break;
1290 if (aChild == mDefaultSubmitElement) {
1291 // Need to reset mDefaultSubmitElement. Do this asynchronously so
1292 // that we're not doing it while the DOM is in flux.
1293 mDefaultSubmitElement = nullptr;
1294 nsContentUtils::AddScriptRunner(new RemoveElementRunnable(this));
1296 // Note that we don't need to notify on the old default submit (which is
1297 // being removed) because it's either being removed from the DOM or
1298 // changing attributes in a way that makes it responsible for sending its
1299 // own notifications.
1302 // If the element was subject to constraint validaton and is invalid, we need
1303 // to update our internal counter.
1304 if (aUpdateValidity) {
1305 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
1306 if (cvElmt &&
1307 cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
1308 UpdateValidity(true);
1312 return rv;
1315 void
1316 HTMLFormElement::HandleDefaultSubmitRemoval()
1318 if (mDefaultSubmitElement) {
1319 // Already got reset somehow; nothing else to do here
1320 return;
1323 if (!mFirstSubmitNotInElements) {
1324 mDefaultSubmitElement = mFirstSubmitInElements;
1325 } else if (!mFirstSubmitInElements) {
1326 mDefaultSubmitElement = mFirstSubmitNotInElements;
1327 } else {
1328 NS_ASSERTION(mFirstSubmitInElements != mFirstSubmitNotInElements,
1329 "How did that happen?");
1330 // Have both; use the earlier one
1331 mDefaultSubmitElement =
1332 CompareFormControlPosition(mFirstSubmitInElements,
1333 mFirstSubmitNotInElements, this) < 0 ?
1334 mFirstSubmitInElements : mFirstSubmitNotInElements;
1337 NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
1338 mDefaultSubmitElement == mFirstSubmitNotInElements,
1339 "What happened here?");
1341 // Notify about change if needed.
1342 if (mDefaultSubmitElement) {
1343 mDefaultSubmitElement->UpdateState(true);
1347 nsresult
1348 HTMLFormElement::RemoveElementFromTableInternal(
1349 nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
1350 nsIContent* aChild, const nsAString& aName)
1352 nsCOMPtr<nsISupports> supports;
1354 if (!aTable.Get(aName, getter_AddRefs(supports)))
1355 return NS_OK;
1357 // Single element in the hash, just remove it if it's the one
1358 // we're trying to remove...
1359 if (supports == aChild) {
1360 aTable.Remove(aName);
1361 ++mExpandoAndGeneration.generation;
1362 return NS_OK;
1365 nsCOMPtr<nsIContent> content(do_QueryInterface(supports));
1366 if (content) {
1367 return NS_OK;
1370 nsCOMPtr<nsIDOMNodeList> nodeList(do_QueryInterface(supports));
1371 NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
1373 // Upcast, uggly, but it works!
1374 nsBaseContentList *list = static_cast<nsBaseContentList*>(nodeList.get());
1376 list->RemoveElement(aChild);
1378 uint32_t length = 0;
1379 list->GetLength(&length);
1381 if (!length) {
1382 // If the list is empty we remove if from our hash, this shouldn't
1383 // happen tho
1384 aTable.Remove(aName);
1385 ++mExpandoAndGeneration.generation;
1386 } else if (length == 1) {
1387 // Only one element left, replace the list in the hash with the
1388 // single element.
1389 nsIContent* node = list->Item(0);
1390 if (node) {
1391 aTable.Put(aName, node);
1395 return NS_OK;
1398 static PLDHashOperator
1399 RemovePastNames(const nsAString& aName,
1400 nsCOMPtr<nsISupports>& aData,
1401 void* aClosure)
1403 return aClosure == aData ? PL_DHASH_REMOVE : PL_DHASH_NEXT;
1406 nsresult
1407 HTMLFormElement::RemoveElementFromTable(nsGenericHTMLFormElement* aElement,
1408 const nsAString& aName,
1409 RemoveElementReason aRemoveReason)
1411 // If the element is being removed from the form, we have to remove it from
1412 // the past names map.
1413 if (aRemoveReason == ElementRemoved) {
1414 uint32_t oldCount = mPastNameLookupTable.Count();
1415 mPastNameLookupTable.Enumerate(RemovePastNames, aElement);
1416 if (oldCount != mPastNameLookupTable.Count()) {
1417 ++mExpandoAndGeneration.generation;
1421 return mControls->RemoveElementFromTable(aElement, aName);
1424 already_AddRefed<nsISupports>
1425 HTMLFormElement::NamedGetter(const nsAString& aName, bool &aFound)
1427 aFound = true;
1429 nsCOMPtr<nsISupports> result = DoResolveName(aName, true);
1430 if (result) {
1431 AddToPastNamesMap(aName, result);
1432 return result.forget();
1435 result = mImageNameLookupTable.GetWeak(aName);
1436 if (result) {
1437 AddToPastNamesMap(aName, result);
1438 return result.forget();
1441 result = mPastNameLookupTable.GetWeak(aName);
1442 if (result) {
1443 return result.forget();
1446 aFound = false;
1447 return nullptr;
1450 bool
1451 HTMLFormElement::NameIsEnumerable(const nsAString& aName)
1453 return true;
1456 void
1457 HTMLFormElement::GetSupportedNames(unsigned, nsTArray<nsString >& aRetval)
1459 // TODO https://www.w3.org/Bugs/Public/show_bug.cgi?id=22320
1462 already_AddRefed<nsISupports>
1463 HTMLFormElement::FindNamedItem(const nsAString& aName,
1464 nsWrapperCache** aCache)
1466 // FIXME Get the wrapper cache from DoResolveName.
1468 bool found;
1469 nsCOMPtr<nsISupports> result = NamedGetter(aName, found);
1470 if (result) {
1471 *aCache = nullptr;
1472 return result.forget();
1475 return nullptr;
1478 already_AddRefed<nsISupports>
1479 HTMLFormElement::DoResolveName(const nsAString& aName,
1480 bool aFlushContent)
1482 nsCOMPtr<nsISupports> result =
1483 mControls->NamedItemInternal(aName, aFlushContent);
1484 return result.forget();
1487 void
1488 HTMLFormElement::OnSubmitClickBegin(nsIContent* aOriginatingElement)
1490 mDeferSubmission = true;
1492 // Prepare to run NotifySubmitObservers early before the
1493 // scripts on the page get to modify the form data, possibly
1494 // throwing off any password manager. (bug 257781)
1495 nsCOMPtr<nsIURI> actionURI;
1496 nsresult rv;
1498 rv = GetActionURL(getter_AddRefs(actionURI), aOriginatingElement);
1499 if (NS_FAILED(rv) || !actionURI)
1500 return;
1502 // Notify observers of submit if the form is valid.
1503 // TODO: checking for mInvalidElementsCount is a temporary fix that should be
1504 // removed with bug 610402.
1505 if (mInvalidElementsCount == 0) {
1506 bool cancelSubmit = false;
1507 rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
1508 if (NS_SUCCEEDED(rv)) {
1509 mNotifiedObservers = true;
1510 mNotifiedObserversResult = cancelSubmit;
1515 void
1516 HTMLFormElement::OnSubmitClickEnd()
1518 mDeferSubmission = false;
1521 void
1522 HTMLFormElement::FlushPendingSubmission()
1524 if (mPendingSubmission) {
1525 // Transfer owning reference so that the submissioin doesn't get deleted
1526 // if we reenter
1527 nsAutoPtr<nsFormSubmission> submission = Move(mPendingSubmission);
1529 SubmitSubmission(submission);
1533 nsresult
1534 HTMLFormElement::GetActionURL(nsIURI** aActionURL,
1535 nsIContent* aOriginatingElement)
1537 nsresult rv = NS_OK;
1539 *aActionURL = nullptr;
1542 // Grab the URL string
1544 // If the originating element is a submit control and has the formaction
1545 // attribute specified, it should be used. Otherwise, the action attribute
1546 // from the form element should be used.
1548 nsAutoString action;
1550 if (aOriginatingElement &&
1551 aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formaction)) {
1552 #ifdef DEBUG
1553 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aOriginatingElement);
1554 NS_ASSERTION(formControl && formControl->IsSubmitControl(),
1555 "The originating element must be a submit form control!");
1556 #endif // DEBUG
1558 nsCOMPtr<nsIDOMHTMLInputElement> inputElement = do_QueryInterface(aOriginatingElement);
1559 if (inputElement) {
1560 inputElement->GetFormAction(action);
1561 } else {
1562 nsCOMPtr<nsIDOMHTMLButtonElement> buttonElement = do_QueryInterface(aOriginatingElement);
1563 if (buttonElement) {
1564 buttonElement->GetFormAction(action);
1565 } else {
1566 NS_ERROR("Originating element must be an input or button element!");
1567 return NS_ERROR_UNEXPECTED;
1570 } else {
1571 GetAction(action);
1575 // Form the full action URL
1578 // Get the document to form the URL.
1579 // We'll also need it later to get the DOM window when notifying form submit
1580 // observers (bug 33203)
1581 if (!IsInDoc()) {
1582 return NS_OK; // No doc means don't submit, see Bug 28988
1585 // Get base URL
1586 nsIDocument *document = OwnerDoc();
1587 nsIURI *docURI = document->GetDocumentURI();
1588 NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
1590 // If an action is not specified and we are inside
1591 // a HTML document then reload the URL. This makes us
1592 // compatible with 4.x browsers.
1593 // If we are in some other type of document such as XML or
1594 // XUL, do nothing. This prevents undesirable reloading of
1595 // a document inside XUL.
1597 nsCOMPtr<nsIURI> actionURL;
1598 if (action.IsEmpty()) {
1599 nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(document));
1600 if (!htmlDoc) {
1601 // Must be a XML, XUL or other non-HTML document type
1602 // so do nothing.
1603 return NS_OK;
1606 rv = docURI->Clone(getter_AddRefs(actionURL));
1607 NS_ENSURE_SUCCESS(rv, rv);
1608 } else {
1609 nsCOMPtr<nsIURI> baseURL = GetBaseURI();
1610 NS_ASSERTION(baseURL, "No Base URL found in Form Submit!\n");
1611 if (!baseURL) {
1612 return NS_OK; // No base URL -> exit early, see Bug 30721
1614 rv = NS_NewURI(getter_AddRefs(actionURL), action, nullptr, baseURL);
1615 NS_ENSURE_SUCCESS(rv, rv);
1619 // Verify the URL should be reached
1621 // Get security manager, check to see if access to action URI is allowed.
1623 nsIScriptSecurityManager *securityManager =
1624 nsContentUtils::GetSecurityManager();
1625 rv = securityManager->
1626 CheckLoadURIWithPrincipal(NodePrincipal(), actionURL,
1627 nsIScriptSecurityManager::STANDARD);
1628 NS_ENSURE_SUCCESS(rv, rv);
1630 // Check if CSP allows this form-action
1631 nsCOMPtr<nsIContentSecurityPolicy> csp;
1632 rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
1633 NS_ENSURE_SUCCESS(rv, rv);
1634 if (csp) {
1635 bool permitsFormAction = true;
1637 // form-action is only enforced if explicitly defined in the
1638 // policy - do *not* consult default-src, see:
1639 // http://www.w3.org/TR/CSP2/#directive-default-src
1640 rv = csp->Permits(actionURL, nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
1641 true, &permitsFormAction);
1642 NS_ENSURE_SUCCESS(rv, rv);
1643 if (!permitsFormAction) {
1644 rv = NS_ERROR_CSP_FORM_ACTION_VIOLATION;
1649 // Assign to the output
1651 *aActionURL = actionURL;
1652 NS_ADDREF(*aActionURL);
1654 return rv;
1657 NS_IMETHODIMP_(nsIFormControl*)
1658 HTMLFormElement::GetDefaultSubmitElement() const
1660 NS_PRECONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
1661 mDefaultSubmitElement == mFirstSubmitNotInElements,
1662 "What happened here?");
1664 return mDefaultSubmitElement;
1667 bool
1668 HTMLFormElement::IsDefaultSubmitElement(const nsIFormControl* aControl) const
1670 NS_PRECONDITION(aControl, "Unexpected call");
1672 if (aControl == mDefaultSubmitElement) {
1673 // Yes, it is
1674 return true;
1677 if (mDefaultSubmitElement ||
1678 (aControl != mFirstSubmitInElements &&
1679 aControl != mFirstSubmitNotInElements)) {
1680 // It isn't
1681 return false;
1684 // mDefaultSubmitElement is null, but we have a non-null submit around
1685 // (aControl, in fact). figure out whether it's in fact the default submit
1686 // and just hasn't been set that way yet. Note that we can't just call
1687 // HandleDefaultSubmitRemoval because we might need to notify to handle that
1688 // correctly and we don't know whether that's safe right here.
1689 if (!mFirstSubmitInElements || !mFirstSubmitNotInElements) {
1690 // We only have one first submit; aControl has to be it
1691 return true;
1694 // We have both kinds of submits. Check which comes first.
1695 nsIFormControl* defaultSubmit =
1696 CompareFormControlPosition(mFirstSubmitInElements,
1697 mFirstSubmitNotInElements, this) < 0 ?
1698 mFirstSubmitInElements : mFirstSubmitNotInElements;
1699 return aControl == defaultSubmit;
1702 bool
1703 HTMLFormElement::ImplicitSubmissionIsDisabled() const
1705 // Input text controls are always in the elements list.
1706 uint32_t numDisablingControlsFound = 0;
1707 uint32_t length = mControls->mElements.Length();
1708 for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
1709 if (mControls->mElements[i]->IsSingleLineTextControl(false) ||
1710 mControls->mElements[i]->GetType() == NS_FORM_INPUT_NUMBER) {
1711 numDisablingControlsFound++;
1714 return numDisablingControlsFound != 1;
1717 NS_IMETHODIMP
1718 HTMLFormElement::GetEncoding(nsAString& aEncoding)
1720 return GetEnctype(aEncoding);
1723 NS_IMETHODIMP
1724 HTMLFormElement::SetEncoding(const nsAString& aEncoding)
1726 return SetEnctype(aEncoding);
1729 int32_t
1730 HTMLFormElement::Length()
1732 return mControls->Length();
1735 NS_IMETHODIMP
1736 HTMLFormElement::GetLength(int32_t* aLength)
1738 *aLength = Length();
1739 return NS_OK;
1742 void
1743 HTMLFormElement::ForgetCurrentSubmission()
1745 mNotifiedObservers = false;
1746 mIsSubmitting = false;
1747 mSubmittingRequest = nullptr;
1748 nsCOMPtr<nsIWebProgress> webProgress = do_QueryReferent(mWebProgress);
1749 if (webProgress) {
1750 webProgress->RemoveProgressListener(this);
1752 mWebProgress = nullptr;
1755 bool
1756 HTMLFormElement::CheckFormValidity(nsIMutableArray* aInvalidElements) const
1758 bool ret = true;
1760 nsTArray<nsGenericHTMLFormElement*> sortedControls;
1761 if (NS_FAILED(mControls->GetSortedControls(sortedControls))) {
1762 return false;
1765 uint32_t len = sortedControls.Length();
1767 // Hold a reference to the elements so they can't be deleted while calling
1768 // the invalid events.
1769 for (uint32_t i = 0; i < len; ++i) {
1770 sortedControls[i]->AddRef();
1773 for (uint32_t i = 0; i < len; ++i) {
1774 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(sortedControls[i]);
1775 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
1776 !cvElmt->IsValid()) {
1777 ret = false;
1778 bool defaultAction = true;
1779 nsContentUtils::DispatchTrustedEvent(sortedControls[i]->OwnerDoc(),
1780 static_cast<nsIContent*>(sortedControls[i]),
1781 NS_LITERAL_STRING("invalid"),
1782 false, true, &defaultAction);
1784 // Add all unhandled invalid controls to aInvalidElements if the caller
1785 // requested them.
1786 if (defaultAction && aInvalidElements) {
1787 aInvalidElements->AppendElement(ToSupports(sortedControls[i]),
1788 false);
1793 // Release the references.
1794 for (uint32_t i = 0; i < len; ++i) {
1795 static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
1798 return ret;
1801 bool
1802 HTMLFormElement::CheckValidFormSubmission()
1805 * Check for form validity: do not submit a form if there are unhandled
1806 * invalid controls in the form.
1807 * This should not be done if the form has been submitted with .submit().
1809 * NOTE: for the moment, we are also checking that there is an observer for
1810 * NS_INVALIDFORMSUBMIT_SUBJECT so it will prevent blocking form submission
1811 * if the browser does not have implemented a UI yet.
1813 * TODO: the check for observer should be removed later when HTML5 Forms will
1814 * be spread enough and authors will assume forms can't be submitted when
1815 * invalid. See bug 587671.
1818 NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate),
1819 "We shouldn't be there if novalidate is set!");
1821 // Don't do validation for a form submit done by a sandboxed document that
1822 // doesn't have 'allow-forms', the submit will have been blocked and the
1823 // HTML5 spec says we shouldn't validate in this case.
1824 nsIDocument* doc = GetComposedDoc();
1825 if (doc && (doc->GetSandboxFlags() & SANDBOXED_FORMS)) {
1826 return true;
1829 // When .submit() is called aEvent = nullptr so we can rely on that to know if
1830 // we have to check the validity of the form.
1831 nsCOMPtr<nsIObserverService> service =
1832 mozilla::services::GetObserverService();
1833 if (!service) {
1834 NS_WARNING("No observer service available!");
1835 return true;
1838 nsCOMPtr<nsISimpleEnumerator> theEnum;
1839 nsresult rv = service->EnumerateObservers(NS_INVALIDFORMSUBMIT_SUBJECT,
1840 getter_AddRefs(theEnum));
1841 // Return true on error here because that's what we always did
1842 NS_ENSURE_SUCCESS(rv, true);
1844 bool hasObserver = false;
1845 rv = theEnum->HasMoreElements(&hasObserver);
1847 // Do not check form validity if there is no observer for
1848 // NS_INVALIDFORMSUBMIT_SUBJECT.
1849 if (NS_SUCCEEDED(rv) && hasObserver) {
1850 nsCOMPtr<nsIMutableArray> invalidElements =
1851 do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
1852 // Return true on error here because that's what we always did
1853 NS_ENSURE_SUCCESS(rv, true);
1855 if (!CheckFormValidity(invalidElements.get())) {
1856 // For the first invalid submission, we should update element states.
1857 // We have to do that _before_ calling the observers so we are sure they
1858 // will not interfere (like focusing the element).
1859 if (!mEverTriedInvalidSubmit) {
1860 mEverTriedInvalidSubmit = true;
1863 * We are going to call update states assuming elements want to
1864 * be notified because we can't know.
1865 * Submissions shouldn't happen during parsing so it _should_ be safe.
1868 nsAutoScriptBlocker scriptBlocker;
1870 for (uint32_t i = 0, length = mControls->mElements.Length();
1871 i < length; ++i) {
1872 // Input elements can trigger a form submission and we want to
1873 // update the style in that case.
1874 if (mControls->mElements[i]->IsHTML(nsGkAtoms::input) &&
1875 nsContentUtils::IsFocusedContent(mControls->mElements[i])) {
1876 static_cast<HTMLInputElement*>(mControls->mElements[i])
1877 ->UpdateValidityUIBits(true);
1880 mControls->mElements[i]->UpdateState(true);
1883 // Because of backward compatibility, <input type='image'> is not in
1884 // elements but can be invalid.
1885 // TODO: should probably be removed when bug 606491 will be fixed.
1886 for (uint32_t i = 0, length = mControls->mNotInElements.Length();
1887 i < length; ++i) {
1888 mControls->mNotInElements[i]->UpdateState(true);
1892 nsCOMPtr<nsISupports> inst;
1893 nsCOMPtr<nsIFormSubmitObserver> observer;
1894 bool more = true;
1895 while (NS_SUCCEEDED(theEnum->HasMoreElements(&more)) && more) {
1896 theEnum->GetNext(getter_AddRefs(inst));
1897 observer = do_QueryInterface(inst);
1899 if (observer) {
1900 observer->NotifyInvalidSubmit(this,
1901 static_cast<nsIArray*>(invalidElements));
1905 // The form is invalid. Observers have been alerted. Do not submit.
1906 return false;
1908 } else {
1909 NS_WARNING("There is no observer for \"invalidformsubmit\". \
1910 One should be implemented!");
1913 return true;
1916 void
1917 HTMLFormElement::UpdateValidity(bool aElementValidity)
1919 if (aElementValidity) {
1920 --mInvalidElementsCount;
1921 } else {
1922 ++mInvalidElementsCount;
1925 NS_ASSERTION(mInvalidElementsCount >= 0, "Something went seriously wrong!");
1927 // The form validity has just changed if:
1928 // - there are no more invalid elements ;
1929 // - or there is one invalid elmement and an element just became invalid.
1930 // If we have invalid elements and we used to before as well, do nothing.
1931 if (mInvalidElementsCount &&
1932 (mInvalidElementsCount != 1 || aElementValidity)) {
1933 return;
1937 * We are going to update states assuming submit controls want to
1938 * be notified because we can't know.
1939 * UpdateValidity shouldn't be called so much during parsing so it _should_
1940 * be safe.
1943 nsAutoScriptBlocker scriptBlocker;
1945 // Inform submit controls that the form validity has changed.
1946 for (uint32_t i = 0, length = mControls->mElements.Length();
1947 i < length; ++i) {
1948 if (mControls->mElements[i]->IsSubmitControl()) {
1949 mControls->mElements[i]->UpdateState(true);
1953 // Because of backward compatibility, <input type='image'> is not in elements
1954 // so we have to check for controls not in elements too.
1955 uint32_t length = mControls->mNotInElements.Length();
1956 for (uint32_t i = 0; i < length; ++i) {
1957 if (mControls->mNotInElements[i]->IsSubmitControl()) {
1958 mControls->mNotInElements[i]->UpdateState(true);
1962 UpdateState(true);
1965 // nsIWebProgressListener
1966 NS_IMETHODIMP
1967 HTMLFormElement::OnStateChange(nsIWebProgress* aWebProgress,
1968 nsIRequest* aRequest,
1969 uint32_t aStateFlags,
1970 nsresult aStatus)
1972 // If STATE_STOP is never fired for any reason (redirect? Failed state
1973 // change?) the form element will leak. It will be kept around by the
1974 // nsIWebProgressListener (assuming it keeps a strong pointer). We will
1975 // consequently leak the request.
1976 if (aRequest == mSubmittingRequest &&
1977 aStateFlags & nsIWebProgressListener::STATE_STOP) {
1978 ForgetCurrentSubmission();
1981 return NS_OK;
1984 NS_IMETHODIMP
1985 HTMLFormElement::OnProgressChange(nsIWebProgress* aWebProgress,
1986 nsIRequest* aRequest,
1987 int32_t aCurSelfProgress,
1988 int32_t aMaxSelfProgress,
1989 int32_t aCurTotalProgress,
1990 int32_t aMaxTotalProgress)
1992 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
1993 return NS_OK;
1996 NS_IMETHODIMP
1997 HTMLFormElement::OnLocationChange(nsIWebProgress* aWebProgress,
1998 nsIRequest* aRequest,
1999 nsIURI* location,
2000 uint32_t aFlags)
2002 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2003 return NS_OK;
2006 NS_IMETHODIMP
2007 HTMLFormElement::OnStatusChange(nsIWebProgress* aWebProgress,
2008 nsIRequest* aRequest,
2009 nsresult aStatus,
2010 const char16_t* aMessage)
2012 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2013 return NS_OK;
2016 NS_IMETHODIMP
2017 HTMLFormElement::OnSecurityChange(nsIWebProgress* aWebProgress,
2018 nsIRequest* aRequest,
2019 uint32_t state)
2021 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
2022 return NS_OK;
2025 NS_IMETHODIMP_(int32_t)
2026 HTMLFormElement::IndexOfControl(nsIFormControl* aControl)
2028 int32_t index = 0;
2029 return mControls->IndexOfControl(aControl, &index) == NS_OK ? index : 0;
2032 void
2033 HTMLFormElement::SetCurrentRadioButton(const nsAString& aName,
2034 HTMLInputElement* aRadio)
2036 mSelectedRadioButtons.Put(aName, aRadio);
2039 HTMLInputElement*
2040 HTMLFormElement::GetCurrentRadioButton(const nsAString& aName)
2042 return mSelectedRadioButtons.GetWeak(aName);
2045 NS_IMETHODIMP
2046 HTMLFormElement::GetNextRadioButton(const nsAString& aName,
2047 const bool aPrevious,
2048 HTMLInputElement* aFocusedRadio,
2049 HTMLInputElement** aRadioOut)
2051 // Return the radio button relative to the focused radio button.
2052 // If no radio is focused, get the radio relative to the selected one.
2053 *aRadioOut = nullptr;
2055 nsRefPtr<HTMLInputElement> currentRadio;
2056 if (aFocusedRadio) {
2057 currentRadio = aFocusedRadio;
2059 else {
2060 mSelectedRadioButtons.Get(aName, getter_AddRefs(currentRadio));
2063 nsCOMPtr<nsISupports> itemWithName = DoResolveName(aName, true);
2064 nsCOMPtr<nsINodeList> radioGroup(do_QueryInterface(itemWithName));
2066 if (!radioGroup) {
2067 return NS_ERROR_FAILURE;
2070 int32_t index = radioGroup->IndexOf(currentRadio);
2071 if (index < 0) {
2072 return NS_ERROR_FAILURE;
2075 uint32_t numRadios;
2076 radioGroup->GetLength(&numRadios);
2077 nsRefPtr<HTMLInputElement> radio;
2079 bool isRadio = false;
2080 do {
2081 if (aPrevious) {
2082 if (--index < 0) {
2083 index = numRadios -1;
2086 else if (++index >= (int32_t)numRadios) {
2087 index = 0;
2089 radio = HTMLInputElement::FromContentOrNull(radioGroup->Item(index));
2090 isRadio = radio && radio->GetType() == NS_FORM_INPUT_RADIO;
2091 if (!isRadio) {
2092 continue;
2095 nsAutoString name;
2096 radio->GetName(name);
2097 isRadio = aName.Equals(name);
2098 } while (!isRadio || (radio->Disabled() && radio != currentRadio));
2100 NS_IF_ADDREF(*aRadioOut = radio);
2101 return NS_OK;
2104 NS_IMETHODIMP
2105 HTMLFormElement::WalkRadioGroup(const nsAString& aName,
2106 nsIRadioVisitor* aVisitor,
2107 bool aFlushContent)
2109 if (aName.IsEmpty()) {
2111 // XXX If the name is empty, it's not stored in the control list. There
2112 // *must* be a more efficient way to do this.
2114 nsCOMPtr<nsIFormControl> control;
2115 uint32_t len = GetElementCount();
2116 for (uint32_t i = 0; i < len; i++) {
2117 control = GetElementAt(i);
2118 if (control->GetType() == NS_FORM_INPUT_RADIO) {
2119 nsCOMPtr<nsIContent> controlContent = do_QueryInterface(control);
2120 if (controlContent &&
2121 controlContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
2122 EmptyString(), eCaseMatters) &&
2123 !aVisitor->Visit(control)) {
2124 break;
2128 return NS_OK;
2131 // Get the control / list of controls from the form using form["name"]
2132 nsCOMPtr<nsISupports> item = DoResolveName(aName, aFlushContent);
2133 if (!item) {
2134 return NS_ERROR_FAILURE;
2137 // If it's just a lone radio button, then select it.
2138 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(item);
2139 if (formControl) {
2140 if (formControl->GetType() == NS_FORM_INPUT_RADIO) {
2141 aVisitor->Visit(formControl);
2143 return NS_OK;
2146 nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(item);
2147 if (!nodeList) {
2148 return NS_OK;
2150 uint32_t length = 0;
2151 nodeList->GetLength(&length);
2152 for (uint32_t i = 0; i < length; i++) {
2153 nsCOMPtr<nsIDOMNode> node;
2154 nodeList->Item(i, getter_AddRefs(node));
2155 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(node);
2156 if (formControl && formControl->GetType() == NS_FORM_INPUT_RADIO &&
2157 !aVisitor->Visit(formControl)) {
2158 break;
2161 return NS_OK;
2164 void
2165 HTMLFormElement::AddToRadioGroup(const nsAString& aName,
2166 nsIFormControl* aRadio)
2168 nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
2169 NS_ASSERTION(element, "radio controls have to be content elements!");
2171 if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
2172 mRequiredRadioButtonCounts.Put(aName,
2173 mRequiredRadioButtonCounts.Get(aName)+1);
2177 void
2178 HTMLFormElement::RemoveFromRadioGroup(const nsAString& aName,
2179 nsIFormControl* aRadio)
2181 nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
2182 NS_ASSERTION(element, "radio controls have to be content elements!");
2184 if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
2185 uint32_t requiredNb = mRequiredRadioButtonCounts.Get(aName);
2186 NS_ASSERTION(requiredNb >= 1,
2187 "At least one radio button has to be required!");
2189 if (requiredNb == 1) {
2190 mRequiredRadioButtonCounts.Remove(aName);
2191 } else {
2192 mRequiredRadioButtonCounts.Put(aName, requiredNb-1);
2197 uint32_t
2198 HTMLFormElement::GetRequiredRadioCount(const nsAString& aName) const
2200 return mRequiredRadioButtonCounts.Get(aName);
2203 void
2204 HTMLFormElement::RadioRequiredWillChange(const nsAString& aName,
2205 bool aRequiredAdded)
2207 if (aRequiredAdded) {
2208 mRequiredRadioButtonCounts.Put(aName,
2209 mRequiredRadioButtonCounts.Get(aName)+1);
2210 } else {
2211 uint32_t requiredNb = mRequiredRadioButtonCounts.Get(aName);
2212 NS_ASSERTION(requiredNb >= 1,
2213 "At least one radio button has to be required!");
2214 if (requiredNb == 1) {
2215 mRequiredRadioButtonCounts.Remove(aName);
2216 } else {
2217 mRequiredRadioButtonCounts.Put(aName, requiredNb-1);
2222 bool
2223 HTMLFormElement::GetValueMissingState(const nsAString& aName) const
2225 return mValueMissingRadioGroups.Get(aName);
2228 void
2229 HTMLFormElement::SetValueMissingState(const nsAString& aName, bool aValue)
2231 mValueMissingRadioGroups.Put(aName, aValue);
2234 EventStates
2235 HTMLFormElement::IntrinsicState() const
2237 EventStates state = nsGenericHTMLElement::IntrinsicState();
2239 if (mInvalidElementsCount) {
2240 state |= NS_EVENT_STATE_INVALID;
2241 } else {
2242 state |= NS_EVENT_STATE_VALID;
2245 return state;
2248 void
2249 HTMLFormElement::Clear()
2251 for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
2252 mImageElements[i]->ClearForm(false);
2254 mImageElements.Clear();
2255 mImageNameLookupTable.Clear();
2256 mPastNameLookupTable.Clear();
2259 namespace {
2261 struct PositionComparator
2263 nsIContent* const mElement;
2264 explicit PositionComparator(nsIContent* const aElement) : mElement(aElement) {}
2266 int operator()(nsIContent* aElement) const {
2267 if (mElement == aElement) {
2268 return 0;
2270 if (nsContentUtils::PositionIsBefore(mElement, aElement)) {
2271 return -1;
2273 return 1;
2277 struct NodeListAdaptor
2279 nsINodeList* const mList;
2280 explicit NodeListAdaptor(nsINodeList* aList) : mList(aList) {}
2281 nsIContent* operator[](size_t aIdx) const {
2282 return mList->Item(aIdx);
2286 } // namespace
2288 nsresult
2289 HTMLFormElement::AddElementToTableInternal(
2290 nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
2291 nsIContent* aChild, const nsAString& aName)
2293 nsCOMPtr<nsISupports> supports;
2294 aTable.Get(aName, getter_AddRefs(supports));
2296 if (!supports) {
2297 // No entry found, add the element
2298 aTable.Put(aName, aChild);
2299 ++mExpandoAndGeneration.generation;
2300 } else {
2301 // Found something in the hash, check its type
2302 nsCOMPtr<nsIContent> content = do_QueryInterface(supports);
2304 if (content) {
2305 // Check if the new content is the same as the one we found in the
2306 // hash, if it is then we leave it in the hash as it is, this will
2307 // happen if a form control has both a name and an id with the same
2308 // value
2309 if (content == aChild) {
2310 return NS_OK;
2313 // Found an element, create a list, add the element to the list and put
2314 // the list in the hash
2315 RadioNodeList *list = new RadioNodeList(this);
2317 // If an element has a @form, we can assume it *might* be able to not have
2318 // a parent and still be in the form.
2319 NS_ASSERTION(content->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
2320 content->GetParent(), "Item in list without parent");
2322 // Determine the ordering between the new and old element.
2323 bool newFirst = nsContentUtils::PositionIsBefore(aChild, content);
2325 list->AppendElement(newFirst ? aChild : content.get());
2326 list->AppendElement(newFirst ? content.get() : aChild);
2329 nsCOMPtr<nsISupports> listSupports = do_QueryObject(list);
2331 // Replace the element with the list.
2332 aTable.Put(aName, listSupports);
2333 } else {
2334 // There's already a list in the hash, add the child to the list
2335 nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports);
2336 NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
2338 // Upcast, uggly, but it works!
2339 RadioNodeList *list =
2340 static_cast<RadioNodeList*>(nodeList.get());
2342 NS_ASSERTION(list->Length() > 1,
2343 "List should have been converted back to a single element");
2345 // Fast-path appends; this check is ok even if the child is
2346 // already in the list, since if it tests true the child would
2347 // have come at the end of the list, and the PositionIsBefore
2348 // will test false.
2349 if (nsContentUtils::PositionIsBefore(list->Item(list->Length() - 1), aChild)) {
2350 list->AppendElement(aChild);
2351 return NS_OK;
2354 // If a control has a name equal to its id, it could be in the
2355 // list already.
2356 if (list->IndexOf(aChild) != -1) {
2357 return NS_OK;
2360 size_t idx;
2361 DebugOnly<bool> found = BinarySearchIf(NodeListAdaptor(list), 0, list->Length(),
2362 PositionComparator(aChild), &idx);
2363 MOZ_ASSERT(!found, "should not have found an element");
2365 list->InsertElementAt(aChild, idx);
2369 return NS_OK;
2372 nsresult
2373 HTMLFormElement::AddImageElement(HTMLImageElement* aChild)
2375 AddElementToList(mImageElements, aChild, this);
2376 return NS_OK;
2379 nsresult
2380 HTMLFormElement::AddImageElementToTable(HTMLImageElement* aChild,
2381 const nsAString& aName)
2383 return AddElementToTableInternal(mImageNameLookupTable, aChild, aName);
2386 nsresult
2387 HTMLFormElement::RemoveImageElement(HTMLImageElement* aChild)
2389 size_t index = mImageElements.IndexOf(aChild);
2390 NS_ENSURE_STATE(index != mImageElements.NoIndex);
2392 mImageElements.RemoveElementAt(index);
2393 return NS_OK;
2396 nsresult
2397 HTMLFormElement::RemoveImageElementFromTable(HTMLImageElement* aElement,
2398 const nsAString& aName,
2399 RemoveElementReason aRemoveReason)
2401 // If the element is being removed from the form, we have to remove it from
2402 // the past names map.
2403 if (aRemoveReason == ElementRemoved) {
2404 mPastNameLookupTable.Enumerate(RemovePastNames, aElement);
2407 return RemoveElementFromTableInternal(mImageNameLookupTable, aElement, aName);
2410 void
2411 HTMLFormElement::AddToPastNamesMap(const nsAString& aName,
2412 nsISupports* aChild)
2414 // If candidates contains exactly one node. Add a mapping from name to the
2415 // node in candidates in the form element's past names map, replacing the
2416 // previous entry with the same name, if any.
2417 nsCOMPtr<nsIContent> node = do_QueryInterface(aChild);
2418 if (node) {
2419 mPastNameLookupTable.Put(aName, aChild);
2423 JSObject*
2424 HTMLFormElement::WrapNode(JSContext* aCx)
2426 return HTMLFormElementBinding::Wrap(aCx, this);
2429 } // namespace dom
2430 } // namespace mozilla