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