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