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