HTMLFormElement.cpp (66388B)
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 <utility> 10 11 #include "Attr.h" 12 #include "jsapi.h" 13 #include "mozilla/AutoRestore.h" 14 #include "mozilla/BasePrincipal.h" 15 #include "mozilla/BinarySearch.h" 16 #include "mozilla/Components.h" 17 #include "mozilla/ContentEvents.h" 18 #include "mozilla/EventDispatcher.h" 19 #include "mozilla/PresShell.h" 20 #include "mozilla/UniquePtr.h" 21 #include "mozilla/dom/BindContext.h" 22 #include "mozilla/dom/BrowsingContext.h" 23 #include "mozilla/dom/CustomEvent.h" 24 #include "mozilla/dom/Document.h" 25 #include "mozilla/dom/HTMLFormControlsCollection.h" 26 #include "mozilla/dom/HTMLFormElementBinding.h" 27 #include "mozilla/dom/TreeOrderedArrayInlines.h" 28 #include "mozilla/dom/nsCSPContext.h" 29 #include "mozilla/dom/nsCSPUtils.h" 30 #include "mozilla/dom/nsMixedContentBlocker.h" 31 #include "nsCOMArray.h" 32 #include "nsContentList.h" 33 #include "nsContentUtils.h" 34 #include "nsDOMAttributeMap.h" 35 #include "nsDocShell.h" 36 #include "nsDocShellLoadState.h" 37 #include "nsError.h" 38 #include "nsFocusManager.h" 39 #include "nsGkAtoms.h" 40 #include "nsHTMLDocument.h" 41 #include "nsInterfaceHashtable.h" 42 #include "nsPresContext.h" 43 #include "nsQueryObject.h" 44 #include "nsStyleConsts.h" 45 #include "nsTArray.h" 46 47 // form submission 48 #include "HTMLFormSubmissionConstants.h" 49 #include "mozilla/StaticPrefs_dom.h" 50 #include "mozilla/StaticPrefs_prompts.h" 51 #include "mozilla/dom/FormData.h" 52 #include "mozilla/dom/FormDataEvent.h" 53 #include "mozilla/dom/SubmitEvent.h" 54 #include "mozilla/glean/DomSecurityMetrics.h" 55 #include "mozilla/intl/Localization.h" 56 #include "nsCategoryManagerUtils.h" 57 #include "nsIContentInlines.h" 58 #include "nsIDocShell.h" 59 #include "nsIInterfaceRequestorUtils.h" 60 #include "nsIPromptService.h" 61 #include "nsIScriptError.h" 62 #include "nsIScriptSecurityManager.h" 63 #include "nsISecurityUITelemetry.h" 64 #include "nsISimpleEnumerator.h" 65 #include "nsNetUtil.h" 66 #include "nsRange.h" 67 68 // radio buttons 69 #include "RadioNodeList.h" 70 #include "mozAutoDocUpdate.h" 71 #include "mozilla/dom/HTMLAnchorElement.h" 72 #include "mozilla/dom/HTMLButtonElement.h" 73 #include "mozilla/dom/HTMLInputElement.h" 74 #include "mozilla/dom/HTMLSelectElement.h" 75 #include "nsIConstraintValidation.h" 76 #include "nsIHTMLCollection.h" 77 #include "nsLayoutUtils.h" 78 #include "nsSandboxFlags.h" 79 80 // images 81 #include "mozilla/dom/HTMLButtonElement.h" 82 #include "mozilla/dom/HTMLImageElement.h" 83 84 // construction, destruction 85 NS_IMPL_NS_NEW_HTML_ELEMENT(Form) 86 87 namespace mozilla::dom { 88 89 static const uint8_t NS_FORM_AUTOCOMPLETE_ON = 1; 90 static const uint8_t NS_FORM_AUTOCOMPLETE_OFF = 0; 91 92 static constexpr nsAttrValue::EnumTableEntry kFormAutocompleteTable[] = { 93 {"on", NS_FORM_AUTOCOMPLETE_ON}, 94 {"off", NS_FORM_AUTOCOMPLETE_OFF}, 95 }; 96 // Default autocomplete value is 'on'. 97 static constexpr const nsAttrValue::EnumTableEntry* kFormDefaultAutocomplete = 98 &kFormAutocompleteTable[0]; 99 100 HTMLFormElement::HTMLFormElement( 101 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 102 : nsGenericHTMLElement(std::move(aNodeInfo)), 103 mControls(new HTMLFormControlsCollection(this)), 104 mPendingSubmission(nullptr), 105 mDefaultSubmitElement(nullptr), 106 mFirstSubmitInElements(nullptr), 107 mFirstSubmitNotInElements(nullptr), 108 mImageNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH), 109 mPastNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH), 110 mSubmitPopupState(PopupBlocker::openAbused), 111 mInvalidElementsCount(0), 112 mFormNumber(-1), 113 mGeneratingSubmit(false), 114 mGeneratingReset(false), 115 mDeferSubmission(false), 116 mNotifiedObservers(false), 117 mNotifiedObserversResult(false), 118 mIsConstructingEntryList(false), 119 mIsFiringSubmissionEvents(false) { 120 // We start out valid. 121 AddStatesSilently(ElementState::VALID); 122 } 123 124 HTMLFormElement::~HTMLFormElement() { 125 if (mControls) { 126 mControls->DropFormReference(); 127 } 128 129 Clear(); 130 } 131 132 // nsISupports 133 134 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormElement) 135 136 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLFormElement, 137 nsGenericHTMLElement) 138 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControls) 139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable) 140 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable) 141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList) 142 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTargetContext) 143 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 144 145 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement, 146 nsGenericHTMLElement) 147 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList) 148 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTargetContext) 149 tmp->Clear(); 150 tmp->mExpandoAndGeneration.OwnerUnlinked(); 151 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 152 153 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLFormElement, 154 nsGenericHTMLElement) 155 156 // EventTarget 157 void HTMLFormElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { 158 if (aEvent->mEventType == u"DOMFormHasPassword"_ns) { 159 mHasPendingPasswordEvent = false; 160 } else if (aEvent->mEventType == u"DOMPossibleUsernameInputAdded"_ns) { 161 mHasPendingPossibleUsernameEvent = false; 162 } 163 } 164 165 nsDOMTokenList* HTMLFormElement::RelList() { 166 if (!mRelList) { 167 mRelList = 168 new nsDOMTokenList(this, nsGkAtoms::rel, sAnchorAndFormRelValues); 169 } 170 return mRelList; 171 } 172 173 NS_IMPL_ELEMENT_CLONE(HTMLFormElement) 174 175 HTMLFormControlsCollection* HTMLFormElement::Elements() { return mControls; } 176 177 void HTMLFormElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, 178 const nsAttrValue* aValue, bool aNotify) { 179 if (aNamespaceID == kNameSpaceID_None) { 180 if (aName == nsGkAtoms::action || aName == nsGkAtoms::target) { 181 // Don't forget we've notified the password manager already if the 182 // page sets the action/target in the during submit. (bug 343182) 183 bool notifiedObservers = mNotifiedObservers; 184 ForgetCurrentSubmission(); 185 mNotifiedObservers = notifiedObservers; 186 } 187 } 188 189 return nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName, aValue, 190 aNotify); 191 } 192 193 void HTMLFormElement::GetAutocomplete(nsAString& aValue) { 194 GetEnumAttr(nsGkAtoms::autocomplete, kFormDefaultAutocomplete->tag, aValue); 195 } 196 197 void HTMLFormElement::GetEnctype(nsAString& aValue) { 198 GetEnumAttr(nsGkAtoms::enctype, kFormDefaultEnctype->tag, aValue); 199 } 200 201 void HTMLFormElement::GetMethod(nsAString& aValue) { 202 GetEnumAttr(nsGkAtoms::method, kFormDefaultMethod->tag, aValue); 203 } 204 205 void HTMLFormElement::ReportInvalidUnfocusableElements( 206 const nsTArray<RefPtr<Element>>&& aInvalidElements) { 207 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); 208 MOZ_ASSERT(focusManager); 209 210 for (const auto& element : aInvalidElements) { 211 bool isFocusable = false; 212 // MOZ_KnownLive because 'aInvalidElements' is guaranteed to keep it alive. 213 // This can go away once bug 1620312 is fixed. 214 focusManager->ElementIsFocusable(MOZ_KnownLive(element), 0, &isFocusable); 215 if (!isFocusable) { 216 nsTArray<nsString> params; 217 nsAutoCString messageName("InvalidFormControlUnfocusable"); 218 219 if (Attr* nameAttr = element->GetAttributes()->GetNamedItem(u"name"_ns)) { 220 nsAutoString name; 221 nameAttr->GetValue(name); 222 params.AppendElement(name); 223 messageName = "InvalidNamedFormControlUnfocusable"; 224 } 225 226 nsContentUtils::ReportToConsole( 227 nsIScriptError::errorFlag, "DOM"_ns, element->GetOwnerDocument(), 228 nsContentUtils::eDOM_PROPERTIES, messageName.get(), params, 229 SourceLocation(element->GetBaseURI())); 230 } 231 } 232 } 233 234 // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit 235 void HTMLFormElement::MaybeSubmit(Element* aSubmitter) { 236 #ifdef DEBUG 237 if (aSubmitter) { 238 const auto* fc = nsIFormControl::FromNode(aSubmitter); 239 MOZ_ASSERT(fc); 240 MOZ_ASSERT(fc->IsSubmitControl(), "aSubmitter is not a submit control?"); 241 } 242 #endif 243 244 // 1-4 of 245 // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit 246 Document* doc = GetComposedDoc(); 247 if (mIsConstructingEntryList || !doc || 248 (doc->GetSandboxFlags() & SANDBOXED_FORMS)) { 249 return; 250 } 251 252 // 5.1. If form's firing submission events is true, then return. 253 if (mIsFiringSubmissionEvents) { 254 return; 255 } 256 257 // 5.2. Set form's firing submission events to true. 258 AutoRestore<bool> resetFiringSubmissionEventsFlag(mIsFiringSubmissionEvents); 259 mIsFiringSubmissionEvents = true; 260 261 // Flag elements as user-interacted. 262 // FIXME: Should be specified, see: 263 // https://github.com/whatwg/html/issues/10066 264 { 265 for (nsGenericHTMLFormElement* el : mControls->mElements.AsSpan()) { 266 el->SetUserInteracted(true); 267 } 268 for (nsGenericHTMLFormElement* el : mControls->mNotInElements.AsSpan()) { 269 el->SetUserInteracted(true); 270 } 271 } 272 273 // 5.3. If the submitter element's no-validate state is false, then 274 // interactively validate the constraints of form and examine the result. 275 // If the result is negative (i.e., the constraint validation concluded 276 // that there were invalid fields and probably informed the user of this) 277 bool noValidateState = 278 HasAttr(nsGkAtoms::novalidate) || 279 (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formnovalidate)); 280 if (!noValidateState && !CheckValidFormSubmission()) { 281 return; 282 } 283 284 // Prepare to run DispatchBeforeSubmitChromeOnlyEvent early before the 285 // scripts on the page get to modify the form data, possibly 286 // throwing off any password manager. (bug 257781) 287 bool cancelSubmit = false; 288 nsresult rv = DispatchBeforeSubmitChromeOnlyEvent(&cancelSubmit); 289 if (NS_SUCCEEDED(rv)) { 290 mNotifiedObservers = true; 291 mNotifiedObserversResult = cancelSubmit; 292 } 293 294 RefPtr<PresShell> presShell = doc->GetPresShell(); 295 if (!presShell) { 296 // We need the nsPresContext for dispatching the submit event. In some 297 // rare cases we need to flush notifications to force creation of the 298 // nsPresContext here (for example when a script calls form.requestSubmit() 299 // from script early during page load). We only flush the notifications 300 // if the PresShell hasn't been created yet, to limit the performance 301 // impact. 302 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames); 303 presShell = doc->GetPresShell(); 304 } 305 306 if (!doc->IsCurrentActiveDocument()) { 307 // Bug 125624 308 return; 309 } 310 311 SubmitEventInit init; 312 init.mBubbles = true; 313 init.mCancelable = true; 314 init.mSubmitter = 315 aSubmitter ? nsGenericHTMLElement::FromNode(aSubmitter) : nullptr; 316 RefPtr<SubmitEvent> event = 317 SubmitEvent::Constructor(this, u"submit"_ns, init); 318 event->SetTrusted(true); 319 nsEventStatus status = nsEventStatus_eIgnore; 320 EventDispatcher::DispatchDOMEvent(this, nullptr, event, nullptr, &status); 321 } 322 323 void HTMLFormElement::MaybeReset(Element* aSubmitter) { 324 if (!OwnerDoc()->IsCurrentActiveDocument()) { 325 // Bug 125624 326 return; 327 } 328 329 InternalFormEvent event(true, eFormReset); 330 event.mOriginator = aSubmitter; 331 nsEventStatus status = nsEventStatus_eIgnore; 332 EventDispatcher::DispatchDOMEvent(this, &event, nullptr, nullptr, &status); 333 } 334 335 void HTMLFormElement::Submit(ErrorResult& aRv) { aRv = DoSubmit(); } 336 337 // https://html.spec.whatwg.org/multipage/forms.html#dom-form-requestsubmit 338 void HTMLFormElement::RequestSubmit(nsGenericHTMLElement* aSubmitter, 339 ErrorResult& aRv) { 340 // 1. If submitter is not null, then: 341 if (aSubmitter) { 342 const auto* fc = nsIFormControl::FromNodeOrNull(aSubmitter); 343 344 // 1.1. If submitter is not a submit button, then throw a TypeError. 345 if (!fc || !fc->IsSubmitControl()) { 346 aRv.ThrowTypeError("The submitter is not a submit button."); 347 return; 348 } 349 350 // 1.2. If submitter's form owner is not this form element, then throw a 351 // "NotFoundError" DOMException. 352 if (fc->GetForm() != this) { 353 aRv.ThrowNotFoundError("The submitter is not owned by this form."); 354 return; 355 } 356 } 357 358 // 2. Otherwise, set submitter to this form element. 359 // 3. Submit this form element, from submitter. 360 MaybeSubmit(aSubmitter); 361 } 362 363 void HTMLFormElement::Reset() { 364 InternalFormEvent event(true, eFormReset); 365 EventDispatcher::Dispatch(this, nullptr, &event); 366 } 367 368 bool HTMLFormElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 369 const nsAString& aValue, 370 nsIPrincipal* aMaybeScriptedPrincipal, 371 nsAttrValue& aResult) { 372 if (aNamespaceID == kNameSpaceID_None) { 373 if (aAttribute == nsGkAtoms::method) { 374 return aResult.ParseEnumValue(aValue, kFormMethodTable, false); 375 } 376 if (aAttribute == nsGkAtoms::enctype) { 377 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false); 378 } 379 if (aAttribute == nsGkAtoms::autocomplete) { 380 return aResult.ParseEnumValue(aValue, kFormAutocompleteTable, false); 381 } 382 } 383 384 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 385 aMaybeScriptedPrincipal, aResult); 386 } 387 388 nsresult HTMLFormElement::BindToTree(BindContext& aContext, nsINode& aParent) { 389 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); 390 NS_ENSURE_SUCCESS(rv, rv); 391 392 if (IsInUncomposedDoc() && aContext.OwnerDoc().IsHTMLOrXHTML()) { 393 aContext.OwnerDoc().AsHTMLDocument()->AddedForm(); 394 } 395 396 return rv; 397 } 398 399 template <typename T> 400 static void MarkOrphans(Span<T*> aArray) { 401 for (auto* element : aArray) { 402 element->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT); 403 } 404 } 405 406 static void CollectOrphans(nsINode* aRemovalRoot, 407 TreeOrderedArray<nsGenericHTMLFormElement*>& aArray 408 #ifdef DEBUG 409 , 410 HTMLFormElement* aThisForm 411 #endif 412 ) { 413 // Put a script blocker around all the notifications we're about to do. 414 nsAutoScriptBlocker scriptBlocker; 415 416 // Walk backwards so that if we remove elements we can just keep iterating 417 uint32_t length = aArray.Length(); 418 for (uint32_t i = length; i > 0; --i) { 419 nsGenericHTMLFormElement* node = aArray[i - 1]; 420 421 // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the 422 // node is in fact a descendant of the form and hence should stay in the 423 // form. If it _is_ set, then we need to check whether the node is a 424 // descendant of aRemovalRoot. If it is, we leave it in the form. 425 #ifdef DEBUG 426 bool removed = false; 427 #endif 428 if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) { 429 node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); 430 if (!node->IsInclusiveDescendantOf(aRemovalRoot)) { 431 nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(node); 432 MOZ_ASSERT(fc); 433 fc->ClearForm(true, false); 434 #ifdef DEBUG 435 removed = true; 436 #endif 437 } 438 } 439 440 #ifdef DEBUG 441 if (!removed) { 442 const auto* fc = nsIFormControl::FromNode(node); 443 MOZ_ASSERT(fc); 444 HTMLFormElement* form = fc->GetForm(); 445 NS_ASSERTION(form == aThisForm, "How did that happen?"); 446 } 447 #endif /* DEBUG */ 448 } 449 } 450 451 static void CollectOrphans(nsINode* aRemovalRoot, 452 const TreeOrderedArray<HTMLImageElement*>& aArray 453 #ifdef DEBUG 454 , 455 HTMLFormElement* aThisForm 456 #endif 457 ) { 458 // Walk backwards so that if we remove elements we can just keep iterating 459 uint32_t length = aArray.Length(); 460 for (uint32_t i = length; i > 0; --i) { 461 HTMLImageElement* node = aArray[i - 1]; 462 463 // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the 464 // node is in fact a descendant of the form and hence should stay in the 465 // form. If it _is_ set, then we need to check whether the node is a 466 // descendant of aRemovalRoot. If it is, we leave it in the form. 467 #ifdef DEBUG 468 bool removed = false; 469 #endif 470 if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) { 471 node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); 472 if (!node->IsInclusiveDescendantOf(aRemovalRoot)) { 473 node->ClearForm(true); 474 475 #ifdef DEBUG 476 removed = true; 477 #endif 478 } 479 } 480 481 #ifdef DEBUG 482 if (!removed) { 483 HTMLFormElement* form = node->GetForm(); 484 NS_ASSERTION(form == aThisForm, "How did that happen?"); 485 } 486 #endif /* DEBUG */ 487 } 488 } 489 490 void HTMLFormElement::UnbindFromTree(UnbindContext& aContext) { 491 MaybeFireFormRemoved(); 492 493 // Note, this is explicitly using uncomposed doc, since we count 494 // only forms in document. 495 RefPtr<Document> oldDocument = GetUncomposedDoc(); 496 497 // Mark all of our controls as maybe being orphans 498 MarkOrphans(mControls->mElements.AsSpan()); 499 MarkOrphans(mControls->mNotInElements.AsSpan()); 500 MarkOrphans(mImageElements.AsSpan()); 501 502 nsGenericHTMLElement::UnbindFromTree(aContext); 503 504 nsINode* ancestor = this; 505 nsINode* cur; 506 do { 507 cur = ancestor->GetParentNode(); 508 if (!cur) { 509 break; 510 } 511 ancestor = cur; 512 } while (true); 513 514 CollectOrphans(ancestor, mControls->mElements 515 #ifdef DEBUG 516 , 517 this 518 #endif 519 ); 520 CollectOrphans(ancestor, mControls->mNotInElements 521 #ifdef DEBUG 522 , 523 this 524 #endif 525 ); 526 CollectOrphans(ancestor, mImageElements 527 #ifdef DEBUG 528 , 529 this 530 #endif 531 ); 532 533 if (oldDocument && oldDocument->IsHTMLOrXHTML()) { 534 oldDocument->AsHTMLDocument()->RemovedForm(); 535 } 536 ForgetCurrentSubmission(); 537 } 538 539 static bool CanSubmit(WidgetEvent& aEvent) { 540 // According to the UI events spec section "Trusted events", we shouldn't 541 // trigger UA default action with an untrusted event except click. 542 // However, there are still some sites depending on sending untrusted event 543 // to submit form, see Bug 1370630. 544 return !StaticPrefs::dom_forms_submit_trusted_event_only() || 545 aEvent.IsTrusted(); 546 } 547 548 void HTMLFormElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 549 aVisitor.mWantsWillHandleEvent = true; 550 if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) && 551 CanSubmit(*aVisitor.mEvent)) { 552 uint32_t msg = aVisitor.mEvent->mMessage; 553 if (msg == eFormSubmit) { 554 if (mGeneratingSubmit) { 555 aVisitor.mCanHandle = false; 556 return; 557 } 558 mGeneratingSubmit = true; 559 560 // XXXedgar, the untrusted event would trigger form submission, in this 561 // case, form need to handle defer flag and flushing pending submission by 562 // itself. This could be removed after Bug 1370630. 563 if (!aVisitor.mEvent->IsTrusted()) { 564 // let the form know that it needs to defer the submission, 565 // that means that if there are scripted submissions, the 566 // latest one will be deferred until after the exit point of the 567 // handler. 568 mDeferSubmission = true; 569 } 570 } else if (msg == eFormReset) { 571 if (mGeneratingReset) { 572 aVisitor.mCanHandle = false; 573 return; 574 } 575 mGeneratingReset = true; 576 } 577 } 578 nsGenericHTMLElement::GetEventTargetParent(aVisitor); 579 } 580 581 void HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor) { 582 // If this is the bubble stage and there is a nested form below us which 583 // received a submit event we do *not* want to handle the submit event 584 // for this form too. 585 if ((aVisitor.mEvent->mMessage == eFormSubmit || 586 aVisitor.mEvent->mMessage == eFormReset) && 587 aVisitor.mEvent->mFlags.mInBubblingPhase && 588 aVisitor.mEvent->mOriginalTarget != static_cast<nsIContent*>(this)) { 589 aVisitor.mEvent->StopPropagation(); 590 } 591 } 592 593 nsresult HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { 594 if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) && 595 CanSubmit(*aVisitor.mEvent)) { 596 EventMessage msg = aVisitor.mEvent->mMessage; 597 if (aVisitor.mEventStatus == nsEventStatus_eIgnore) { 598 switch (msg) { 599 case eFormReset: { 600 DoReset(); 601 break; 602 } 603 case eFormSubmit: { 604 if (!aVisitor.mEvent->IsTrusted()) { 605 // Warning about the form submission is from untrusted event. 606 OwnerDoc()->WarnOnceAbout( 607 DeprecatedOperations::eFormSubmissionUntrustedEvent); 608 } 609 RefPtr<Event> event = aVisitor.mDOMEvent; 610 DoSubmit(event); 611 break; 612 } 613 default: 614 break; 615 } 616 } 617 618 // XXXedgar, the untrusted event would trigger form submission, in this 619 // case, form need to handle defer flag and flushing pending submission by 620 // itself. This could be removed after Bug 1370630. 621 if (msg == eFormSubmit && !aVisitor.mEvent->IsTrusted()) { 622 // let the form know not to defer subsequent submissions 623 mDeferSubmission = false; 624 // tell the form to flush a possible pending submission. 625 FlushPendingSubmission(); 626 } 627 628 if (msg == eFormSubmit) { 629 mGeneratingSubmit = false; 630 } else if (msg == eFormReset) { 631 mGeneratingReset = false; 632 } 633 } 634 return NS_OK; 635 } 636 637 nsresult HTMLFormElement::DoReset() { 638 // Make sure the presentation is up-to-date 639 if (Document* doc = GetComposedDoc()) { 640 doc->FlushPendingNotifications(FlushType::ContentAndNotify); 641 } 642 643 // JBK walk the elements[] array instead of form frame controls - bug 34297 644 uint32_t numElements = mControls->Length(); 645 for (uint32_t elementX = 0; elementX < numElements; ++elementX) { 646 // Hold strong ref in case the reset does something weird 647 if (elementX >= mControls->mElements.Length()) { 648 continue; 649 } 650 nsCOMPtr<nsIFormControl> controlNode = 651 nsIFormControl::FromNode(mControls->mElements[elementX]); 652 if (controlNode) { 653 controlNode->Reset(); 654 } 655 } 656 657 return NS_OK; 658 } 659 660 #define NS_ENSURE_SUBMIT_SUCCESS(rv) \ 661 if (NS_FAILED(rv)) { \ 662 ForgetCurrentSubmission(); \ 663 return rv; \ 664 } 665 666 nsresult HTMLFormElement::DoSubmit(Event* aEvent) { 667 Document* doc = GetComposedDoc(); 668 NS_ASSERTION(doc, "Should never get here without a current doc"); 669 670 // Make sure the presentation is up-to-date 671 if (doc) { 672 doc->FlushPendingNotifications(FlushType::ContentAndNotify); 673 } 674 675 // Don't submit if we're not in a document or if we're in 676 // a sandboxed frame and form submit is disabled. 677 if (mIsConstructingEntryList || !doc || 678 (doc->GetSandboxFlags() & SANDBOXED_FORMS)) { 679 return NS_OK; 680 } 681 682 if (IsSubmitting()) { 683 NS_WARNING("Preventing double form submission"); 684 // XXX Should this return an error? 685 return NS_OK; 686 } 687 688 mTargetContext = nullptr; 689 mCurrentLoadId = Nothing(); 690 691 UniquePtr<HTMLFormSubmission> submission; 692 693 // 694 // prepare the submission object 695 // 696 nsresult rv = BuildSubmission(getter_Transfers(submission), aEvent); 697 698 // Don't raise an error if form cannot navigate. 699 if (rv == NS_ERROR_NOT_AVAILABLE) { 700 return NS_OK; 701 } 702 703 NS_ENSURE_SUCCESS(rv, rv); 704 705 // XXXbz if the script global is that for an sXBL/XBL2 doc, it won't 706 // be a window... 707 nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow(); 708 if (window) { 709 mSubmitPopupState = PopupBlocker::GetPopupControlState(); 710 } else { 711 mSubmitPopupState = PopupBlocker::openAbused; 712 } 713 714 // 715 // perform the submission 716 // 717 if (!submission) { 718 #ifdef DEBUG 719 HTMLDialogElement* dialog = nullptr; 720 for (nsIContent* parent = GetParent(); parent; 721 parent = parent->GetParent()) { 722 dialog = HTMLDialogElement::FromNodeOrNull(parent); 723 if (dialog) { 724 break; 725 } 726 } 727 MOZ_ASSERT(!dialog || !dialog->Open()); 728 #endif 729 return NS_OK; 730 } 731 732 if (DialogFormSubmission* dialogSubmission = 733 submission->GetAsDialogSubmission()) { 734 return SubmitDialog(dialogSubmission); 735 } 736 737 if (mDeferSubmission) { 738 // we are in an event handler, JS submitted so we have to 739 // defer this submission. let's remember it and return 740 // without submitting 741 mPendingSubmission = std::move(submission); 742 return NS_OK; 743 } 744 745 return SubmitSubmission(submission.get()); 746 } 747 748 nsresult HTMLFormElement::BuildSubmission(HTMLFormSubmission** aFormSubmission, 749 Event* aEvent) { 750 // Get the submitter element 751 nsGenericHTMLElement* submitter = nullptr; 752 if (aEvent) { 753 SubmitEvent* submitEvent = aEvent->AsSubmitEvent(); 754 if (submitEvent) { 755 submitter = submitEvent->GetSubmitter(); 756 } 757 } 758 759 nsresult rv; 760 761 // 762 // Walk over the form elements and call SubmitNamesValues() on them to get 763 // their data. 764 // 765 auto encoding = GetSubmitEncoding()->OutputEncoding(); 766 RefPtr<FormData> formData = 767 new FormData(GetOwnerGlobal(), encoding, submitter); 768 rv = ConstructEntryList(formData); 769 NS_ENSURE_SUBMIT_SUCCESS(rv); 770 771 // Step 9. If form cannot navigate, then return. 772 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm 773 if (!GetComposedDoc()) { 774 return NS_ERROR_NOT_AVAILABLE; 775 } 776 777 // 778 // Get the submission object 779 // 780 rv = HTMLFormSubmission::GetFromForm(this, submitter, encoding, formData, 781 aFormSubmission); 782 NS_ENSURE_SUBMIT_SUCCESS(rv); 783 784 // 785 // Dump the data into the submission object 786 // 787 if (!(*aFormSubmission)->GetAsDialogSubmission()) { 788 rv = formData->CopySubmissionDataTo(*aFormSubmission); 789 NS_ENSURE_SUBMIT_SUCCESS(rv); 790 } 791 792 return NS_OK; 793 } 794 795 nsresult HTMLFormElement::SubmitSubmission( 796 HTMLFormSubmission* aFormSubmission) { 797 MOZ_ASSERT(!mDeferSubmission); 798 MOZ_ASSERT(!mPendingSubmission); 799 800 nsCOMPtr<nsIURI> actionURI = aFormSubmission->GetActionURL(); 801 if (!actionURI) { 802 return NS_OK; 803 } 804 805 // If there is no link handler, then we won't actually be able to submit. 806 Document* doc = GetComposedDoc(); 807 RefPtr<nsDocShell> container = 808 doc ? nsDocShell::Cast(doc->GetDocShell()) : nullptr; 809 if (!container || IsEditable()) { 810 return NS_OK; 811 } 812 813 // javascript URIs are not really submissions; they just call a function. 814 // Also, they may synchronously call submit(), and we want them to be able to 815 // do so while still disallowing other double submissions. (Bug 139798) 816 // Note that any other URI types that are of equivalent type should also be 817 // added here. 818 // XXXbz this is a mess. The real issue here is that nsJSChannel sets the 819 // LOAD_BACKGROUND flag, so doesn't notify us, compounded by the fact that 820 // the JS executes before we forget the submission in OnStateChange on 821 // STATE_STOP. As a result, we have to make sure that we simply pretend 822 // we're not submitting when submitting to a JS URL. That's kinda bogus, but 823 // there we are. 824 bool schemeIsJavaScript = actionURI->SchemeIs("javascript"); 825 826 // 827 // Notify observers of submit 828 // 829 nsresult rv; 830 bool cancelSubmit = false; 831 if (mNotifiedObservers) { 832 cancelSubmit = mNotifiedObserversResult; 833 } else { 834 rv = DispatchBeforeSubmitChromeOnlyEvent(&cancelSubmit); 835 NS_ENSURE_SUBMIT_SUCCESS(rv); 836 } 837 838 if (cancelSubmit) { 839 return NS_OK; 840 } 841 842 cancelSubmit = false; 843 rv = DoSecureToInsecureSubmitCheck(actionURI, &cancelSubmit); 844 NS_ENSURE_SUBMIT_SUCCESS(rv); 845 846 if (cancelSubmit) { 847 return NS_OK; 848 } 849 850 // 851 // Submit 852 // 853 uint64_t currentLoadId = 0; 854 855 { 856 AutoPopupStatePusher popupStatePusher(mSubmitPopupState); 857 858 AutoHandlingUserInputStatePusher userInpStatePusher( 859 aFormSubmission->IsInitiatedFromUserInput()); 860 861 nsCOMPtr<nsIInputStream> postDataStream; 862 rv = aFormSubmission->GetEncodedSubmission( 863 actionURI, getter_AddRefs(postDataStream), actionURI); 864 NS_ENSURE_SUBMIT_SUCCESS(rv); 865 866 nsAutoString target; 867 aFormSubmission->GetTarget(target); 868 869 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(actionURI); 870 loadState->SetTarget(target); 871 loadState->SetPostDataStream(postDataStream); 872 loadState->SetFirstParty(true); 873 loadState->SetIsFormSubmission(true); 874 loadState->SetTriggeringPrincipal(NodePrincipal()); 875 loadState->SetPrincipalToInherit(NodePrincipal()); 876 loadState->SetPolicyContainer(GetPolicyContainer()); 877 loadState->SetAllowFocusMove(UserActivation::IsHandlingUserInput()); 878 879 const bool hasValidUserGestureActivation = 880 doc->HasValidTransientUserGestureActivation(); 881 loadState->SetHasValidUserGestureActivation(hasValidUserGestureActivation); 882 loadState->SetTextDirectiveUserActivation( 883 doc->ConsumeTextDirectiveUserActivation() || 884 hasValidUserGestureActivation); 885 loadState->SetFormDataEntryList(aFormSubmission->GetFormData()); 886 if (aFormSubmission->IsInitiatedFromUserInput()) { 887 loadState->SetUserNavigationInvolvement( 888 UserNavigationInvolvement::Activation); 889 } 890 if (FormData* formData = aFormSubmission->GetFormData(); 891 formData && formData->GetSubmitterElement()) { 892 loadState->SetSourceElement(formData->GetSubmitterElement()); 893 } else { 894 loadState->SetSourceElement(this); 895 } 896 897 nsCOMPtr<nsIPrincipal> nodePrincipal = NodePrincipal(); 898 rv = container->OnLinkClickSync(this, loadState, false, nodePrincipal); 899 NS_ENSURE_SUBMIT_SUCCESS(rv); 900 901 mTargetContext = loadState->TargetBrowsingContext().GetMaybeDiscarded(); 902 currentLoadId = loadState->GetLoadIdentifier(); 903 } 904 905 // Even if the submit succeeds, it's possible for there to be no 906 // browsing context; for example, if it's to a named anchor within 907 // the same page the submit will not really do anything. 908 if (mTargetContext && !mTargetContext->IsDiscarded() && !schemeIsJavaScript) { 909 mCurrentLoadId = Some(currentLoadId); 910 } else { 911 ForgetCurrentSubmission(); 912 } 913 914 return rv; 915 } 916 917 // https://html.spec.whatwg.org/#concept-form-submit step 11 918 nsresult HTMLFormElement::SubmitDialog(DialogFormSubmission* aFormSubmission) { 919 // Close the dialog subject. If there is a result, let that be the return 920 // value. 921 HTMLDialogElement* dialog = aFormSubmission->DialogElement(); 922 MOZ_ASSERT(dialog); 923 924 Optional<nsAString> retValue; 925 retValue = &aFormSubmission->ReturnValue(); 926 dialog->Close(retValue); 927 928 return NS_OK; 929 } 930 931 nsresult HTMLFormElement::DoSecureToInsecureSubmitCheck(nsIURI* aActionURL, 932 bool* aCancelSubmit) { 933 *aCancelSubmit = false; 934 935 if (!StaticPrefs::security_warn_submit_secure_to_insecure()) { 936 return NS_OK; 937 } 938 939 // Only ask the user about posting from a secure URI to an insecure URI if 940 // this element is in the root document. When this is not the case, the mixed 941 // content blocker will take care of security for us. 942 if (!OwnerDoc()->IsTopLevelContentDocument()) { 943 return NS_OK; 944 } 945 946 if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aActionURL)) { 947 return NS_OK; 948 } 949 950 if (nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(aActionURL)) { 951 return NS_OK; 952 } 953 954 if (nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(aActionURL)) { 955 return NS_OK; 956 } 957 958 nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow(); 959 if (!window) { 960 return NS_ERROR_FAILURE; 961 } 962 963 // Now that we know the action URI is insecure check if we're submitting from 964 // a secure URI and if so fall thru and prompt user about posting. 965 if (nsCOMPtr<nsPIDOMWindowInner> innerWindow = OwnerDoc()->GetInnerWindow()) { 966 if (!innerWindow->IsSecureContext()) { 967 return NS_OK; 968 } 969 } 970 971 // Bug 1351358: While file URIs are considered to be secure contexts we allow 972 // submitting a form to an insecure URI from a file URI without an alert in an 973 // attempt to avoid compatibility issues. 974 if (window->GetDocumentURI()->SchemeIs("file")) { 975 return NS_OK; 976 } 977 978 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); 979 if (!docShell) { 980 return NS_ERROR_FAILURE; 981 } 982 983 nsresult rv; 984 nsCOMPtr<nsIPromptService> promptSvc = 985 do_GetService("@mozilla.org/prompter;1", &rv); 986 if (NS_FAILED(rv)) { 987 return rv; 988 } 989 990 nsTArray<nsCString> resIds = {"toolkit/global/htmlForm.ftl"_ns}; 991 RefPtr<intl::Localization> l10n = intl::Localization::Create(resIds, true); 992 nsAutoCString title; 993 nsAutoCString message; 994 nsAutoCString cont; 995 ErrorResult error; 996 l10n->FormatValueSync("form-post-secure-to-insecure-warning-title"_ns, {}, 997 title, error); 998 NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult()); 999 l10n->FormatValueSync("form-post-secure-to-insecure-warning-message"_ns, {}, 1000 message, error); 1001 NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult()); 1002 l10n->FormatValueSync("form-post-secure-to-insecure-warning-continue"_ns, {}, 1003 cont, error); 1004 NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult()); 1005 int32_t buttonPressed; 1006 bool checkState = 1007 false; // this is unused (ConfirmEx requires this parameter) 1008 rv = promptSvc->ConfirmExBC( 1009 docShell->GetBrowsingContext(), 1010 StaticPrefs::prompts_modalType_insecureFormSubmit(), 1011 NS_ConvertUTF8toUTF16(title).get(), NS_ConvertUTF8toUTF16(message).get(), 1012 (nsIPromptService::BUTTON_TITLE_IS_STRING * 1013 nsIPromptService::BUTTON_POS_0) + 1014 (nsIPromptService::BUTTON_TITLE_CANCEL * 1015 nsIPromptService::BUTTON_POS_1), 1016 NS_ConvertUTF8toUTF16(cont).get(), nullptr, nullptr, nullptr, &checkState, 1017 &buttonPressed); 1018 if (NS_FAILED(rv)) { 1019 return rv; 1020 } 1021 *aCancelSubmit = (buttonPressed == 1); 1022 uint32_t telemetryBucket = 1023 nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE; 1024 mozilla::glean::security_ui::events.AccumulateSingleSample(telemetryBucket); 1025 if (!*aCancelSubmit) { 1026 // The user opted to continue, so note that in the next telemetry bucket. 1027 mozilla::glean::security_ui::events.AccumulateSingleSample(telemetryBucket + 1028 1); 1029 } 1030 return NS_OK; 1031 } 1032 1033 nsresult HTMLFormElement::DispatchBeforeSubmitChromeOnlyEvent( 1034 bool* aCancelSubmit) { 1035 bool defaultAction = true; 1036 nsresult rv = nsContentUtils::DispatchEventOnlyToChrome( 1037 OwnerDoc(), static_cast<nsINode*>(this), u"DOMFormBeforeSubmit"_ns, 1038 CanBubble::eYes, Cancelable::eYes, &defaultAction); 1039 *aCancelSubmit = !defaultAction; 1040 if (*aCancelSubmit) { 1041 return NS_OK; 1042 } 1043 return rv; 1044 } 1045 1046 nsresult HTMLFormElement::ConstructEntryList(FormData* aFormData) { 1047 MOZ_ASSERT(aFormData, "Must have FormData!"); 1048 if (mIsConstructingEntryList) { 1049 // Step 2.2 of https://xhr.spec.whatwg.org/#dom-formdata. 1050 return NS_ERROR_DOM_INVALID_STATE_ERR; 1051 } 1052 1053 AutoRestore<bool> resetConstructingEntryList(mIsConstructingEntryList); 1054 mIsConstructingEntryList = true; 1055 // This shouldn't be called recursively, so use a rather large value 1056 // for the preallocated buffer. 1057 AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls; 1058 nsresult rv = mControls->GetSortedControls(sortedControls); 1059 NS_ENSURE_SUCCESS(rv, rv); 1060 1061 // Walk the list of nodes and call SubmitNamesValues() on the controls 1062 for (nsGenericHTMLFormElement* control : sortedControls) { 1063 // Disabled elements don't submit 1064 if (!control->IsDisabled()) { 1065 nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(control); 1066 MOZ_ASSERT(fc); 1067 // Tell the control to submit its name/value pairs to the submission 1068 fc->SubmitNamesValues(aFormData); 1069 } 1070 } 1071 1072 FormDataEventInit init; 1073 init.mBubbles = true; 1074 init.mCancelable = false; 1075 init.mFormData = aFormData; 1076 RefPtr<FormDataEvent> event = 1077 FormDataEvent::Constructor(this, u"formdata"_ns, init); 1078 event->SetTrusted(true); 1079 1080 EventDispatcher::DispatchDOMEvent(this, nullptr, event, nullptr, nullptr); 1081 1082 return NS_OK; 1083 } 1084 1085 NotNull<const Encoding*> HTMLFormElement::GetSubmitEncoding() { 1086 nsAutoString acceptCharsetValue; 1087 GetAttr(nsGkAtoms::acceptcharset, acceptCharsetValue); 1088 1089 int32_t charsetLen = acceptCharsetValue.Length(); 1090 if (charsetLen > 0) { 1091 int32_t offset = 0; 1092 int32_t spPos = 0; 1093 // get charset from charsets one by one 1094 do { 1095 spPos = acceptCharsetValue.FindChar(char16_t(' '), offset); 1096 int32_t cnt = ((-1 == spPos) ? (charsetLen - offset) : (spPos - offset)); 1097 if (cnt > 0) { 1098 nsAutoString uCharset; 1099 acceptCharsetValue.Mid(uCharset, offset, cnt); 1100 1101 auto encoding = Encoding::ForLabelNoReplacement(uCharset); 1102 if (encoding) { 1103 return WrapNotNull(encoding); 1104 } 1105 } 1106 offset = spPos + 1; 1107 } while (spPos != -1); 1108 } 1109 // if there are no accept-charset or all the charset are not supported 1110 // Get the charset from document 1111 Document* doc = GetComposedDoc(); 1112 if (doc) { 1113 return doc->GetDocumentCharacterSet(); 1114 } 1115 return UTF_8_ENCODING; 1116 } 1117 1118 Element* HTMLFormElement::IndexedGetter(uint32_t aIndex, bool& aFound) { 1119 Element* element = mControls->mElements.SafeElementAt(aIndex, nullptr); 1120 aFound = element != nullptr; 1121 return element; 1122 } 1123 1124 nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild, 1125 bool aUpdateValidity, bool aNotify) { 1126 // If an element has a @form, we can assume it *might* be able to not have 1127 // a parent and still be in the form. 1128 NS_ASSERTION(aChild->HasAttr(nsGkAtoms::form) || aChild->GetParent(), 1129 "Form control should have a parent"); 1130 nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(aChild); 1131 MOZ_ASSERT(fc); 1132 // Determine whether to add the new element to the elements or 1133 // the not-in-elements list. 1134 bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(fc); 1135 TreeOrderedArray<nsGenericHTMLFormElement*>& controlList = 1136 childInElements ? mControls->mElements : mControls->mNotInElements; 1137 1138 const size_t insertedIndex = controlList.Insert(*aChild, this); 1139 const bool lastElement = controlList.Length() == insertedIndex + 1; 1140 1141 auto type = fc->ControlType(); 1142 1143 // Default submit element handling 1144 if (fc->IsSubmitControl()) { 1145 // Update mDefaultSubmitElement, mFirstSubmitInElements, 1146 // mFirstSubmitNotInElements. 1147 1148 nsGenericHTMLFormElement** firstSubmitSlot = 1149 childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements; 1150 1151 // The new child is the new first submit in its list if the firstSubmitSlot 1152 // is currently empty or if the child is before what's currently in the 1153 // slot. Note that if we already have a control in firstSubmitSlot and 1154 // we're appending this element can't possibly replace what's currently in 1155 // the slot. Also note that aChild can't become the mDefaultSubmitElement 1156 // unless it replaces what's in the slot. If it _does_ replace what's in 1157 // the slot, it becomes the default submit if either the default submit is 1158 // what's in the slot or the child is earlier than the default submit. 1159 if (!*firstSubmitSlot || 1160 (!lastElement && nsContentUtils::CompareTreePosition<TreeKind::DOM>( 1161 aChild, *firstSubmitSlot, this) < 0)) { 1162 // Update mDefaultSubmitElement if it's currently in a valid state. 1163 // Valid state means either non-null or null because there are in fact 1164 // no submit elements around. 1165 if ((mDefaultSubmitElement || 1166 (!mFirstSubmitInElements && !mFirstSubmitNotInElements)) && 1167 (*firstSubmitSlot == mDefaultSubmitElement || 1168 nsContentUtils::CompareTreePosition<TreeKind::DOM>( 1169 aChild, mDefaultSubmitElement, this) < 0)) { 1170 SetDefaultSubmitElement(aChild); 1171 } 1172 *firstSubmitSlot = aChild; 1173 } 1174 1175 MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements || 1176 mDefaultSubmitElement == mFirstSubmitNotInElements || 1177 !mDefaultSubmitElement, 1178 "What happened here?"); 1179 } 1180 1181 // If the element is subject to constraint validaton and is invalid, we need 1182 // to update our internal counter. 1183 if (aUpdateValidity) { 1184 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild); 1185 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && 1186 !cvElmt->IsValid()) { 1187 UpdateValidity(false); 1188 } 1189 } 1190 1191 // Notify the radio button it's been added to a group 1192 // This has to be done _after_ UpdateValidity() call to prevent the element 1193 // being count twice. 1194 if (type == FormControlType::InputRadio) { 1195 RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild); 1196 radio->AddToRadioGroup(); 1197 } 1198 1199 return NS_OK; 1200 } 1201 1202 nsresult HTMLFormElement::AddElementToTable(nsGenericHTMLFormElement* aChild, 1203 const nsAString& aName) { 1204 return mControls->AddElementToTable(aChild, aName); 1205 } 1206 1207 void HTMLFormElement::SetDefaultSubmitElement( 1208 nsGenericHTMLFormElement* aElement) { 1209 if (mDefaultSubmitElement) { 1210 // It just so happens that a radio button or an <option> can't be our 1211 // default submit element, so we can just blindly remove the bit. 1212 mDefaultSubmitElement->RemoveStates(ElementState::DEFAULT); 1213 } 1214 mDefaultSubmitElement = aElement; 1215 if (mDefaultSubmitElement) { 1216 mDefaultSubmitElement->AddStates(ElementState::DEFAULT); 1217 } 1218 } 1219 1220 nsresult HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild, 1221 bool aUpdateValidity) { 1222 RemoveElementFromPastNamesMap(aChild); 1223 1224 // 1225 // Remove it from the radio group if it's a radio button 1226 // 1227 nsresult rv = NS_OK; 1228 nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(aChild); 1229 MOZ_ASSERT(fc); 1230 if (fc->ControlType() == FormControlType::InputRadio) { 1231 RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild); 1232 radio->RemoveFromRadioGroup(); 1233 } 1234 1235 // Determine whether to remove the child from the elements list 1236 // or the not in elements list. 1237 bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(fc); 1238 TreeOrderedArray<nsGenericHTMLFormElement*>& controls = 1239 childInElements ? mControls->mElements : mControls->mNotInElements; 1240 1241 // Find the index of the child. This will be used later if necessary 1242 // to find the default submit. 1243 size_t index = controls.IndexOf(aChild); 1244 NS_ENSURE_STATE(index != controls.NoIndex); 1245 1246 controls.RemoveElementAt(index); 1247 1248 // Update our mFirstSubmit* values. 1249 nsGenericHTMLFormElement** firstSubmitSlot = 1250 childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements; 1251 if (aChild == *firstSubmitSlot) { 1252 *firstSubmitSlot = nullptr; 1253 1254 // We are removing the first submit in this list, find the new first submit 1255 uint32_t length = controls.Length(); 1256 for (uint32_t i = index; i < length; ++i) { 1257 const auto* currentControl = 1258 nsIFormControl::FromNode(controls.ElementAt(i)); 1259 MOZ_ASSERT(currentControl); 1260 if (currentControl->IsSubmitControl()) { 1261 *firstSubmitSlot = controls.ElementAt(i); 1262 break; 1263 } 1264 } 1265 } 1266 1267 if (aChild == mDefaultSubmitElement) { 1268 // Need to reset mDefaultSubmitElement. Do this asynchronously so 1269 // that we're not doing it while the DOM is in flux. 1270 SetDefaultSubmitElement(nullptr); 1271 nsContentUtils::AddScriptRunner(new RemoveElementRunnable(this)); 1272 1273 // Note that we don't need to notify on the old default submit (which is 1274 // being removed) because it's either being removed from the DOM or 1275 // changing attributes in a way that makes it responsible for sending its 1276 // own notifications. 1277 } 1278 1279 // If the element was subject to constraint validation and is invalid, we need 1280 // to update our internal counter. 1281 if (aUpdateValidity) { 1282 nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild); 1283 if (cvElmt && cvElmt->IsCandidateForConstraintValidation() && 1284 !cvElmt->IsValid()) { 1285 UpdateValidity(true); 1286 } 1287 } 1288 1289 return rv; 1290 } 1291 1292 void HTMLFormElement::HandleDefaultSubmitRemoval() { 1293 if (mDefaultSubmitElement) { 1294 // Already got reset somehow; nothing else to do here 1295 return; 1296 } 1297 1298 nsGenericHTMLFormElement* newDefaultSubmit; 1299 if (!mFirstSubmitNotInElements) { 1300 newDefaultSubmit = mFirstSubmitInElements; 1301 } else if (!mFirstSubmitInElements) { 1302 newDefaultSubmit = mFirstSubmitNotInElements; 1303 } else { 1304 NS_ASSERTION(mFirstSubmitInElements != mFirstSubmitNotInElements, 1305 "How did that happen?"); 1306 // Have both; use the earlier one 1307 newDefaultSubmit = 1308 nsContentUtils::CompareTreePosition<TreeKind::DOM>( 1309 mFirstSubmitInElements, mFirstSubmitNotInElements, this) < 0 1310 ? mFirstSubmitInElements 1311 : mFirstSubmitNotInElements; 1312 } 1313 SetDefaultSubmitElement(newDefaultSubmit); 1314 1315 MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements || 1316 mDefaultSubmitElement == mFirstSubmitNotInElements, 1317 "What happened here?"); 1318 } 1319 1320 nsresult HTMLFormElement::RemoveElementFromTableInternal( 1321 nsInterfaceHashtable<nsStringHashKey, nsISupports>& aTable, 1322 nsIContent* aChild, const nsAString& aName) { 1323 auto entry = aTable.Lookup(aName); 1324 if (!entry) { 1325 return NS_OK; 1326 } 1327 // Single element in the hash, just remove it if it's the one 1328 // we're trying to remove... 1329 if (entry.Data() == aChild) { 1330 entry.Remove(); 1331 ++mExpandoAndGeneration.generation; 1332 return NS_OK; 1333 } 1334 1335 nsCOMPtr<nsIContent> content(do_QueryInterface(entry.Data())); 1336 if (content) { 1337 return NS_OK; 1338 } 1339 1340 // If it's not a content node then it must be a RadioNodeList. 1341 MOZ_ASSERT(nsCOMPtr<RadioNodeList>(do_QueryInterface(entry.Data()))); 1342 auto* list = static_cast<RadioNodeList*>(entry->get()); 1343 1344 list->RemoveElement(aChild); 1345 1346 uint32_t length = list->Length(); 1347 1348 if (!length) { 1349 // If the list is empty we remove if from our hash, this shouldn't 1350 // happen tho 1351 entry.Remove(); 1352 ++mExpandoAndGeneration.generation; 1353 } else if (length == 1) { 1354 // Only one element left, replace the list in the hash with the 1355 // single element. 1356 nsIContent* node = list->Item(0); 1357 if (node) { 1358 entry.Data() = node; 1359 } 1360 } 1361 1362 return NS_OK; 1363 } 1364 1365 nsresult HTMLFormElement::RemoveElementFromTable( 1366 nsGenericHTMLFormElement* aElement, const nsAString& aName) { 1367 return mControls->RemoveElementFromTable(aElement, aName); 1368 } 1369 1370 already_AddRefed<nsISupports> HTMLFormElement::ResolveName( 1371 const nsAString& aName) { 1372 nsCOMPtr<nsISupports> result = mControls->NamedItemInternal(aName); 1373 if (result) { 1374 AddToPastNamesMap(aName, result); 1375 return result.forget(); 1376 } 1377 1378 result = mImageNameLookupTable.GetWeak(aName); 1379 if (result) { 1380 AddToPastNamesMap(aName, result); 1381 return result.forget(); 1382 } 1383 1384 result = mPastNameLookupTable.GetWeak(aName); 1385 return result.forget(); 1386 } 1387 1388 already_AddRefed<nsISupports> HTMLFormElement::NamedGetter( 1389 const nsAString& aName, bool& aFound) { 1390 if (nsCOMPtr<nsISupports> result = ResolveName(aName)) { 1391 aFound = true; 1392 return result.forget(); 1393 } 1394 1395 aFound = false; 1396 return nullptr; 1397 } 1398 1399 void HTMLFormElement::GetSupportedNames(nsTArray<nsString>& aRetval) { 1400 // TODO https://github.com/whatwg/html/issues/1731 1401 } 1402 1403 void HTMLFormElement::OnSubmitClickBegin() { mDeferSubmission = true; } 1404 1405 void HTMLFormElement::OnSubmitClickEnd() { mDeferSubmission = false; } 1406 1407 void HTMLFormElement::FlushPendingSubmission() { 1408 MOZ_ASSERT(!mDeferSubmission); 1409 1410 if (mPendingSubmission) { 1411 // Transfer owning reference so that the submission doesn't get deleted 1412 // if we reenter 1413 UniquePtr<HTMLFormSubmission> submission = std::move(mPendingSubmission); 1414 1415 SubmitSubmission(submission.get()); 1416 } 1417 } 1418 1419 void HTMLFormElement::GetAction(nsString& aValue) { 1420 if (!GetAttr(nsGkAtoms::action, aValue) || aValue.IsEmpty()) { 1421 Document* document = OwnerDoc(); 1422 nsIURI* docURI = document->GetDocumentURI(); 1423 if (docURI) { 1424 nsAutoCString spec; 1425 nsresult rv = docURI->GetSpec(spec); 1426 if (NS_FAILED(rv)) { 1427 return; 1428 } 1429 1430 CopyUTF8toUTF16(spec, aValue); 1431 } 1432 } else { 1433 GetURIAttr(nsGkAtoms::action, nullptr, aValue); 1434 } 1435 } 1436 1437 nsresult HTMLFormElement::GetActionURL(nsIURI** aActionURL, 1438 Element* aOriginatingElement) { 1439 nsresult rv = NS_OK; 1440 1441 *aActionURL = nullptr; 1442 1443 // 1444 // Grab the URL string 1445 // 1446 // If the originating element is a submit control and has the formaction 1447 // attribute specified, it should be used. Otherwise, the action attribute 1448 // from the form element should be used. 1449 // 1450 nsAutoString action; 1451 1452 if (aOriginatingElement && 1453 aOriginatingElement->HasAttr(nsGkAtoms::formaction)) { 1454 #ifdef DEBUG 1455 const auto* formControl = nsIFormControl::FromNode(aOriginatingElement); 1456 NS_ASSERTION(formControl && formControl->IsSubmitControl(), 1457 "The originating element must be a submit form control!"); 1458 #endif // DEBUG 1459 1460 HTMLInputElement* inputElement = 1461 HTMLInputElement::FromNode(aOriginatingElement); 1462 if (inputElement) { 1463 inputElement->GetFormAction(action); 1464 } else { 1465 auto buttonElement = HTMLButtonElement::FromNode(aOriginatingElement); 1466 if (buttonElement) { 1467 buttonElement->GetFormAction(action); 1468 } else { 1469 NS_ERROR("Originating element must be an input or button element!"); 1470 return NS_ERROR_UNEXPECTED; 1471 } 1472 } 1473 } else { 1474 GetAction(action); 1475 } 1476 1477 // 1478 // Form the full action URL 1479 // 1480 1481 // Get the document to form the URL. 1482 // We'll also need it later to get the DOM window when notifying form submit 1483 // observers (bug 33203) 1484 if (!IsInComposedDoc()) { 1485 return NS_OK; // No doc means don't submit, see Bug 28988 1486 } 1487 1488 // Get base URL 1489 Document* document = OwnerDoc(); 1490 nsIURI* docURI = document->GetDocumentURI(); 1491 NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED); 1492 1493 // If an action is not specified and we are inside 1494 // a HTML document then reload the URL. This makes us 1495 // compatible with 4.x browsers. 1496 // If we are in some other type of document such as XML or 1497 // XUL, do nothing. This prevents undesirable reloading of 1498 // a document inside XUL. 1499 1500 nsCOMPtr<nsIURI> actionURL; 1501 if (action.IsEmpty()) { 1502 if (!document->IsHTMLOrXHTML()) { 1503 // Must be a XML, XUL or other non-HTML document type 1504 // so do nothing. 1505 return NS_OK; 1506 } 1507 1508 actionURL = docURI; 1509 } else { 1510 nsIURI* baseURL = GetBaseURI(); 1511 NS_ASSERTION(baseURL, "No Base URL found in Form Submit!\n"); 1512 if (!baseURL) { 1513 return NS_OK; // No base URL -> exit early, see Bug 30721 1514 } 1515 rv = NS_NewURI(getter_AddRefs(actionURL), action, nullptr, baseURL); 1516 NS_ENSURE_SUCCESS(rv, rv); 1517 } 1518 1519 // 1520 // Verify the URL should be reached 1521 // 1522 // Get security manager, check to see if access to action URI is allowed. 1523 // 1524 nsIScriptSecurityManager* securityManager = 1525 nsContentUtils::GetSecurityManager(); 1526 rv = securityManager->CheckLoadURIWithPrincipal( 1527 NodePrincipal(), actionURL, nsIScriptSecurityManager::STANDARD, 1528 OwnerDoc()->InnerWindowID()); 1529 NS_ENSURE_SUCCESS(rv, rv); 1530 1531 // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. In 1532 // such a case we have to upgrade the action url from http:// to https://. 1533 // The upgrade is only required if the actionURL is http and not a potentially 1534 // trustworthy loopback URI. 1535 bool needsUpgrade = 1536 actionURL->SchemeIs("http") && 1537 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(actionURL) && 1538 document->GetUpgradeInsecureRequests(false); 1539 if (needsUpgrade) { 1540 // let's use the old specification before the upgrade for logging 1541 AutoTArray<nsString, 2> params; 1542 nsAutoCString spec; 1543 rv = actionURL->GetSpec(spec); 1544 NS_ENSURE_SUCCESS(rv, rv); 1545 CopyUTF8toUTF16(spec, *params.AppendElement()); 1546 1547 // upgrade the actionURL from http:// to use https:// 1548 nsCOMPtr<nsIURI> upgradedActionURL; 1549 rv = NS_GetSecureUpgradedURI(actionURL, getter_AddRefs(upgradedActionURL)); 1550 NS_ENSURE_SUCCESS(rv, rv); 1551 actionURL = std::move(upgradedActionURL); 1552 1553 // let's log a message to the console that we are upgrading a request 1554 nsAutoCString scheme; 1555 rv = actionURL->GetScheme(scheme); 1556 NS_ENSURE_SUCCESS(rv, rv); 1557 CopyUTF8toUTF16(scheme, *params.AppendElement()); 1558 1559 CSP_LogLocalizedStr( 1560 "upgradeInsecureRequest", params, 1561 ""_ns, // aSourceFile 1562 u""_ns, // aScriptSample 1563 0, // aLineNumber 1564 1, // aColumnNumber 1565 nsIScriptError::warningFlag, "upgradeInsecureRequest"_ns, 1566 document->InnerWindowID(), 1567 document->NodePrincipal()->OriginAttributesRef().IsPrivateBrowsing()); 1568 } 1569 1570 // 1571 // Assign to the output 1572 // 1573 actionURL.forget(aActionURL); 1574 1575 return rv; 1576 } 1577 1578 void HTMLFormElement::GetSubmissionTarget(nsGenericHTMLElement* aSubmitter, 1579 nsAString& aTarget) { 1580 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm 1581 // 19. If the submitter element is a submit button and it has a formtarget 1582 // attribute, then set formTarget to the formtarget attribute value. 1583 // 20. Let target be the result of getting an element's target given 1584 // submitter's form owner and formTarget. 1585 // 1586 // Note: Falling back to the base target is part of "get an element's target". 1587 if (!(aSubmitter && aSubmitter->GetAttr(nsGkAtoms::formtarget, aTarget)) && 1588 !GetAttr(nsGkAtoms::target, aTarget)) { 1589 GetBaseTarget(aTarget); 1590 } 1591 SanitizeLinkOrFormTarget(aTarget); 1592 } 1593 1594 nsGenericHTMLFormElement* HTMLFormElement::GetDefaultSubmitElement() const { 1595 MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements || 1596 mDefaultSubmitElement == mFirstSubmitNotInElements, 1597 "What happened here?"); 1598 1599 return mDefaultSubmitElement; 1600 } 1601 1602 bool HTMLFormElement::ImplicitSubmissionIsDisabled() const { 1603 // Input text controls are always in the elements list. 1604 uint32_t numDisablingControlsFound = 0; 1605 for (auto* element : mControls->mElements.AsSpan()) { 1606 const auto* fc = nsIFormControl::FromNode(element); 1607 MOZ_ASSERT(fc); 1608 if (fc->IsSingleLineTextControl(false)) { 1609 numDisablingControlsFound++; 1610 if (numDisablingControlsFound > 1) { 1611 break; 1612 } 1613 } 1614 } 1615 return numDisablingControlsFound != 1; 1616 } 1617 1618 bool HTMLFormElement::IsLastActiveElement( 1619 const nsGenericHTMLFormElement* aElement) const { 1620 MOZ_ASSERT(aElement, "Unexpected call"); 1621 1622 // See bug 2007450 for why this temporary is needed. 1623 Span elements = mControls->mElements.AsSpan(); 1624 for (auto* element : Reversed(elements)) { 1625 const auto* fc = nsIFormControl::FromNode(element); 1626 MOZ_ASSERT(fc); 1627 // XXX How about date/time control? 1628 if (fc->IsTextControl(false) && !element->IsDisabled()) { 1629 return element == aElement; 1630 } 1631 } 1632 return false; 1633 } 1634 1635 int32_t HTMLFormElement::Length() { return mControls->Length(); } 1636 1637 void HTMLFormElement::ForgetCurrentSubmission() { 1638 mNotifiedObservers = false; 1639 mTargetContext = nullptr; 1640 mCurrentLoadId = Nothing(); 1641 } 1642 1643 bool HTMLFormElement::CheckFormValidity( 1644 nsTArray<RefPtr<Element>>* aInvalidElements) const { 1645 bool ret = true; 1646 1647 // This shouldn't be called recursively, so use a rather large value 1648 // for the preallocated buffer. 1649 AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls; 1650 if (NS_FAILED(mControls->GetSortedControls(sortedControls))) { 1651 return false; 1652 } 1653 1654 uint32_t len = sortedControls.Length(); 1655 1656 for (uint32_t i = 0; i < len; ++i) { 1657 nsCOMPtr<nsIConstraintValidation> cvElmt = 1658 do_QueryObject(sortedControls[i]); 1659 bool defaultAction = true; 1660 if (cvElmt && !cvElmt->CheckValidity(*sortedControls[i], &defaultAction)) { 1661 ret = false; 1662 1663 // Add all unhandled invalid controls to aInvalidElements if the caller 1664 // requested them. 1665 if (defaultAction && aInvalidElements) { 1666 aInvalidElements->AppendElement(sortedControls[i]); 1667 } 1668 } 1669 } 1670 1671 return ret; 1672 } 1673 1674 bool HTMLFormElement::CheckValidFormSubmission() { 1675 /** 1676 * Check for form validity: do not submit a form if there are unhandled 1677 * invalid controls in the form. 1678 * This should not be done if the form has been submitted with .submit() or 1679 * has been submitted and novalidate/formnovalidate is used. 1680 * 1681 * NOTE: for the moment, we are also checking that whether the MozInvalidForm 1682 * event gets prevented default so it will prevent blocking form submission if 1683 * the browser does not have implemented a UI yet. 1684 * 1685 * TODO: the check for MozInvalidForm event should be removed later when HTML5 1686 * Forms will be spread enough and authors will assume forms can't be 1687 * submitted when invalid. See bug 587671. 1688 */ 1689 1690 AutoTArray<RefPtr<Element>, 32> invalidElements; 1691 if (CheckFormValidity(&invalidElements)) { 1692 return true; 1693 } 1694 1695 AutoJSAPI jsapi; 1696 if (!jsapi.Init(GetOwnerGlobal())) { 1697 return false; 1698 } 1699 JS::Rooted<JS::Value> detail(jsapi.cx()); 1700 if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) { 1701 return false; 1702 } 1703 1704 RefPtr<CustomEvent> event = 1705 NS_NewDOMCustomEvent(OwnerDoc(), nullptr, nullptr); 1706 event->InitCustomEvent(jsapi.cx(), u"MozInvalidForm"_ns, 1707 /* CanBubble */ true, 1708 /* Cancelable */ true, detail); 1709 event->SetTrusted(true); 1710 event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; 1711 1712 DispatchEvent(*event); 1713 1714 ReportInvalidUnfocusableElements(std::move(invalidElements)); 1715 1716 return !event->DefaultPrevented(); 1717 } 1718 1719 void HTMLFormElement::UpdateValidity(bool aElementValidity) { 1720 if (aElementValidity) { 1721 --mInvalidElementsCount; 1722 } else { 1723 ++mInvalidElementsCount; 1724 } 1725 1726 NS_ASSERTION(mInvalidElementsCount >= 0, "Something went seriously wrong!"); 1727 1728 // The form validity has just changed if: 1729 // - there are no more invalid elements ; 1730 // - or there is one invalid elmement and an element just became invalid. 1731 // If we have invalid elements and we used to before as well, do nothing. 1732 if (mInvalidElementsCount && 1733 (mInvalidElementsCount != 1 || aElementValidity)) { 1734 return; 1735 } 1736 1737 AutoStateChangeNotifier notifier(*this, true); 1738 RemoveStatesSilently(ElementState::VALID | ElementState::INVALID); 1739 AddStatesSilently(mInvalidElementsCount ? ElementState::INVALID 1740 : ElementState::VALID); 1741 } 1742 1743 int32_t HTMLFormElement::IndexOfContent(nsIContent* aContent) { 1744 int32_t index = 0; 1745 return mControls->IndexOfContent(aContent, &index) == NS_OK ? index : 0; 1746 } 1747 1748 void HTMLFormElement::Clear() { 1749 for (HTMLImageElement* image : mImageElements.AsSpan()) { 1750 image->ClearForm(false); 1751 } 1752 mImageElements.Clear(); 1753 mImageNameLookupTable.Clear(); 1754 mPastNameLookupTable.Clear(); 1755 } 1756 1757 namespace { 1758 1759 struct PositionComparator { 1760 nsIContent* const mElement; 1761 mutable nsContentUtils::NodeIndexCache mCache; 1762 explicit PositionComparator(nsIContent* const aElement) 1763 : mElement(aElement) {} 1764 1765 int operator()(nsIContent* aElement) const { 1766 if (mElement == aElement) { 1767 return 0; 1768 } 1769 return nsContentUtils::CompareTreePosition<TreeKind::DOM>( 1770 mElement, aElement, nullptr, &mCache); 1771 } 1772 }; 1773 1774 struct RadioNodeListAdaptor { 1775 RadioNodeList* const mList; 1776 explicit RadioNodeListAdaptor(RadioNodeList* aList) : mList(aList) {} 1777 nsIContent* operator[](size_t aIdx) const { return mList->Item(aIdx); } 1778 }; 1779 1780 } // namespace 1781 1782 nsresult HTMLFormElement::AddElementToTableInternal( 1783 nsInterfaceHashtable<nsStringHashKey, nsISupports>& aTable, 1784 nsIContent* aChild, const nsAString& aName) { 1785 return aTable.WithEntryHandle(aName, [&](auto&& entry) { 1786 if (!entry) { 1787 // No entry found, add the element 1788 entry.Insert(aChild); 1789 ++mExpandoAndGeneration.generation; 1790 } else { 1791 // Found something in the hash, check its type 1792 nsCOMPtr<nsIContent> content = do_QueryInterface(entry.Data()); 1793 1794 if (content) { 1795 // Check if the new content is the same as the one we found in the 1796 // hash, if it is then we leave it in the hash as it is, this will 1797 // happen if a form control has both a name and an id with the same 1798 // value 1799 if (content == aChild) { 1800 return NS_OK; 1801 } 1802 1803 // Found an element, create a list, add the element to the list and put 1804 // the list in the hash 1805 RadioNodeList* list = new RadioNodeList(this); 1806 1807 // If an element has a @form, we can assume it *might* be able to not 1808 // have a parent and still be in the form. 1809 NS_ASSERTION( 1810 (content->IsElement() && content->AsElement()->HasAttr( 1811 kNameSpaceID_None, nsGkAtoms::form)) || 1812 content->GetParent(), 1813 "Item in list without parent"); 1814 1815 // Determine the ordering between the new and old element. 1816 bool newFirst = nsContentUtils::PositionIsBefore(aChild, content); 1817 1818 list->AppendElement(newFirst ? aChild : content.get()); 1819 list->AppendElement(newFirst ? content.get() : aChild); 1820 1821 nsCOMPtr<nsISupports> listSupports = do_QueryObject(list); 1822 1823 // Replace the element with the list. 1824 entry.Data() = listSupports; 1825 } else { 1826 // There's already a list in the hash, add the child to the list. 1827 MOZ_ASSERT(nsCOMPtr<RadioNodeList>(do_QueryInterface(entry.Data()))); 1828 auto* list = static_cast<RadioNodeList*>(entry->get()); 1829 1830 NS_ASSERTION( 1831 list->Length() > 1, 1832 "List should have been converted back to a single element"); 1833 1834 PositionComparator cmp(aChild); 1835 1836 // Fast-path appends; this check is ok even if the child is 1837 // already in the list, since if it tests true the child would 1838 // have come at the end of the list, and the PositionIsBefore 1839 // will test false. 1840 if (cmp(list->Item(list->Length() - 1)) > 0) { 1841 list->AppendElement(aChild); 1842 return NS_OK; 1843 } 1844 1845 size_t idx; 1846 const bool found = BinarySearchIf(RadioNodeListAdaptor(list), 0, 1847 list->Length(), cmp, &idx); 1848 if (found && 1849 (list->Item(idx) == aChild || list->IndexOf(aChild) != -1)) { 1850 // If a control has a name equal to its id, it could be in the list 1851 // already. Also, found could be true mid-unbind even though the node 1852 // is not the same. That's a temporarily-broken state. 1853 return NS_OK; 1854 } 1855 list->InsertElementAt(aChild, idx); 1856 } 1857 } 1858 1859 return NS_OK; 1860 }); 1861 } 1862 1863 nsresult HTMLFormElement::AddImageElement(HTMLImageElement* aElement) { 1864 mImageElements.Insert(*aElement, this); 1865 return NS_OK; 1866 } 1867 1868 nsresult HTMLFormElement::AddImageElementToTable(HTMLImageElement* aChild, 1869 const nsAString& aName) { 1870 return AddElementToTableInternal(mImageNameLookupTable, aChild, aName); 1871 } 1872 1873 nsresult HTMLFormElement::RemoveImageElement(HTMLImageElement* aElement) { 1874 RemoveElementFromPastNamesMap(aElement); 1875 mImageElements.RemoveElement(*aElement); 1876 return NS_OK; 1877 } 1878 1879 nsresult HTMLFormElement::RemoveImageElementFromTable( 1880 HTMLImageElement* aElement, const nsAString& aName) { 1881 return RemoveElementFromTableInternal(mImageNameLookupTable, aElement, aName); 1882 } 1883 1884 void HTMLFormElement::AddToPastNamesMap(const nsAString& aName, 1885 nsISupports* aChild) { 1886 // If candidates contains exactly one node. Add a mapping from name to the 1887 // node in candidates in the form element's past names map, replacing the 1888 // previous entry with the same name, if any. 1889 nsCOMPtr<nsIContent> node = do_QueryInterface(aChild); 1890 if (node) { 1891 mPastNameLookupTable.InsertOrUpdate(aName, ToSupports(node)); 1892 node->SetFlags(MAY_BE_IN_PAST_NAMES_MAP); 1893 } 1894 } 1895 1896 void HTMLFormElement::RemoveElementFromPastNamesMap(Element* aElement) { 1897 if (!aElement->HasFlag(MAY_BE_IN_PAST_NAMES_MAP)) { 1898 return; 1899 } 1900 1901 aElement->UnsetFlags(MAY_BE_IN_PAST_NAMES_MAP); 1902 1903 uint32_t oldCount = mPastNameLookupTable.Count(); 1904 for (auto iter = mPastNameLookupTable.Iter(); !iter.Done(); iter.Next()) { 1905 if (aElement == iter.Data()) { 1906 iter.Remove(); 1907 } 1908 } 1909 if (oldCount != mPastNameLookupTable.Count()) { 1910 ++mExpandoAndGeneration.generation; 1911 } 1912 } 1913 1914 JSObject* HTMLFormElement::WrapNode(JSContext* aCx, 1915 JS::Handle<JSObject*> aGivenProto) { 1916 return HTMLFormElement_Binding::Wrap(aCx, this, aGivenProto); 1917 } 1918 1919 int32_t HTMLFormElement::GetFormNumberForStateKey() { 1920 if (mFormNumber == -1) { 1921 mFormNumber = OwnerDoc()->GetNextFormNumber(); 1922 } 1923 return mFormNumber; 1924 } 1925 1926 void HTMLFormElement::NodeInfoChanged(Document* aOldDoc) { 1927 nsGenericHTMLElement::NodeInfoChanged(aOldDoc); 1928 1929 // When a <form> element is adopted into a new document, we want any state 1930 // keys generated from it to no longer consider this element to be parser 1931 // inserted, and so have state keys based on the position of the <form> 1932 // element in the document, rather than the order it was inserted in. 1933 // 1934 // This is not strictly necessary, since we only ever look at the form number 1935 // for parser inserted form controls, and we do that at the time the form 1936 // control element is inserted into its original document by the parser. 1937 mFormNumber = -1; 1938 } 1939 1940 bool HTMLFormElement::IsSubmitting() const { 1941 bool loading = mTargetContext && !mTargetContext->IsDiscarded() && 1942 mCurrentLoadId && 1943 mTargetContext->IsLoadingIdentifier(*mCurrentLoadId); 1944 return loading; 1945 } 1946 1947 void HTMLFormElement::MaybeFireFormRemoved() { 1948 // We want this event to be fired only when the form is removed from the DOM 1949 // tree, not when it is released (ex, tab is closed). So don't fire an event 1950 // when the form doesn't have a docshell. 1951 Document* doc = GetComposedDoc(); 1952 nsIDocShell* container = doc ? doc->GetDocShell() : nullptr; 1953 if (!container) { 1954 return; 1955 } 1956 1957 // Right now, only the password manager and formautofill listen to the event 1958 // and only listen to it under certain circumstances. So don't fire this event 1959 // unless necessary. 1960 if (!doc->ShouldNotifyFormOrPasswordRemoved()) { 1961 return; 1962 } 1963 1964 AsyncEventDispatcher::RunDOMEventWhenSafe( 1965 *this, u"DOMFormRemoved"_ns, CanBubble::eNo, ChromeOnlyDispatch::eYes); 1966 } 1967 1968 } // namespace mozilla::dom