tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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