tor-browser

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

HTMLSelectElement.cpp (54677B)


      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/HTMLSelectElement.h"
      8 
      9 #include "mozAutoDocUpdate.h"
     10 #include "mozilla/BasicEvents.h"
     11 #include "mozilla/EventDispatcher.h"
     12 #include "mozilla/MappedDeclarationsBuilder.h"
     13 #include "mozilla/Maybe.h"
     14 #include "mozilla/PresState.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "mozilla/dom/Element.h"
     17 #include "mozilla/dom/FormData.h"
     18 #include "mozilla/dom/HTMLOptGroupElement.h"
     19 #include "mozilla/dom/HTMLOptionElement.h"
     20 #include "mozilla/dom/HTMLSelectElementBinding.h"
     21 #include "mozilla/dom/UnionTypes.h"
     22 #include "mozilla/dom/WindowGlobalChild.h"
     23 #include "nsComboboxControlFrame.h"
     24 #include "nsContentCreatorFunctions.h"
     25 #include "nsContentList.h"
     26 #include "nsContentUtils.h"
     27 #include "nsError.h"
     28 #include "nsGkAtoms.h"
     29 #include "nsIFrame.h"
     30 #include "nsISelectControlFrame.h"
     31 #include "nsLayoutUtils.h"
     32 #include "nsListControlFrame.h"
     33 #include "nsServiceManagerUtils.h"
     34 #include "nsStyleConsts.h"
     35 #include "nsTextNode.h"
     36 
     37 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select)
     38 
     39 namespace mozilla::dom {
     40 
     41 //----------------------------------------------------------------------
     42 //
     43 // SafeOptionListMutation
     44 //
     45 
     46 SafeOptionListMutation::SafeOptionListMutation(nsIContent* aSelect,
     47                                               nsIContent* aParent,
     48                                               nsIContent* aKid,
     49                                               uint32_t aIndex, bool aNotify)
     50    : mSelect(HTMLSelectElement::FromNodeOrNull(aSelect)),
     51      mTopLevelMutation(false),
     52      mNeedsRebuild(false),
     53      mNotify(aNotify) {
     54  if (mSelect) {
     55    mInitialSelectedOption = mSelect->Item(mSelect->SelectedIndex());
     56    mTopLevelMutation = !mSelect->mMutating;
     57    if (mTopLevelMutation) {
     58      mSelect->mMutating = true;
     59    } else {
     60      // This is very unfortunate, but to handle mutation events properly,
     61      // option list must be up-to-date before inserting or removing options.
     62      // Fortunately this is called only if mutation event listener
     63      // adds or removes options.
     64      mSelect->RebuildOptionsArray(mNotify);
     65    }
     66    nsresult rv;
     67    if (aKid) {
     68      rv = mSelect->WillAddOptions(aKid, aParent, aIndex, mNotify);
     69    } else {
     70      rv = mSelect->WillRemoveOptions(aParent, aIndex, mNotify);
     71    }
     72    mNeedsRebuild = NS_FAILED(rv);
     73  }
     74 }
     75 
     76 SafeOptionListMutation::~SafeOptionListMutation() {
     77  if (mSelect) {
     78    if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) {
     79      mSelect->RebuildOptionsArray(true);
     80    }
     81    if (mTopLevelMutation) {
     82      mSelect->mMutating = false;
     83    }
     84    if (mSelect->Item(mSelect->SelectedIndex()) != mInitialSelectedOption) {
     85      // We must have triggered the SelectSomething() codepath, which can cause
     86      // our validity to change.  Unfortunately, our attempt to update validity
     87      // in that case may not have worked correctly, because we actually call it
     88      // before we have inserted the new <option>s into the DOM!  Go ahead and
     89      // update validity here as needed, because by now we know our <option>s
     90      // are where they should be.
     91      mSelect->UpdateValueMissingValidityState();
     92      mSelect->UpdateValidityElementStates(mNotify);
     93    }
     94 #ifdef DEBUG
     95    mSelect->VerifyOptionsArray();
     96 #endif
     97  }
     98 }
     99 
    100 //----------------------------------------------------------------------
    101 //
    102 // HTMLSelectElement
    103 //
    104 
    105 // construction, destruction
    106 
    107 HTMLSelectElement::HTMLSelectElement(
    108    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
    109    FromParser aFromParser)
    110    : nsGenericHTMLFormControlElementWithState(
    111          std::move(aNodeInfo), aFromParser, FormControlType::Select),
    112      mOptions(new HTMLOptionsCollection(this)),
    113      mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
    114      mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown),
    115      mIsDoneAddingChildren(!aFromParser),
    116      mDisabledChanged(false),
    117      mMutating(false),
    118      mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
    119      mUserInteracted(false),
    120      mDefaultSelectionSet(false),
    121      mIsOpenInParentProcess(false),
    122      mNonOptionChildren(0),
    123      mOptGroupCount(0),
    124      mSelectedIndex(-1) {
    125  SetHasWeirdParserInsertionMode();
    126 
    127  // DoneAddingChildren() will be called later if it's from the parser,
    128  // otherwise it is
    129 
    130  // Set up our default state: enabled, optional, and valid.
    131  AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ |
    132                    ElementState::VALID);
    133 }
    134 
    135 // ISupports
    136 
    137 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSelectElement)
    138 
    139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
    140    HTMLSelectElement, nsGenericHTMLFormControlElementWithState)
    141  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
    142  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOptions)
    143  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedOptions)
    144 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    145 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
    146    HTMLSelectElement, nsGenericHTMLFormControlElementWithState)
    147  NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
    148  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedOptions)
    149 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    150 
    151 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(
    152    HTMLSelectElement, nsGenericHTMLFormControlElementWithState,
    153    nsIConstraintValidation)
    154 
    155 // nsIDOMHTMLSelectElement
    156 
    157 NS_IMPL_ELEMENT_CLONE(HTMLSelectElement)
    158 
    159 void HTMLSelectElement::SetCustomValidity(const nsAString& aError) {
    160  ConstraintValidation::SetCustomValidity(aError);
    161  UpdateValidityElementStates(true);
    162 }
    163 
    164 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
    165 void HTMLSelectElement::ShowPicker(ErrorResult& aRv) {
    166  // Step 1. If this is not mutable, then throw an "InvalidStateError"
    167  // DOMException.
    168  if (IsDisabled()) {
    169    return aRv.ThrowInvalidStateError("This select is disabled.");
    170  }
    171 
    172  // Step 2. If this's relevant settings object's origin is not same origin with
    173  // this's relevant settings object's top-level origin, and this is a select
    174  // element, [...], then throw a "SecurityError" DOMException.
    175  nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
    176  WindowGlobalChild* windowGlobalChild =
    177      window ? window->GetWindowGlobalChild() : nullptr;
    178  if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) {
    179    return aRv.ThrowSecurityError(
    180        "Call was blocked because the current origin isn't same-origin with "
    181        "top.");
    182  }
    183 
    184  // Step 3. If this's relevant global object does not have transient
    185  // activation, then throw a "NotAllowedError" DOMException.
    186  if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
    187    return aRv.ThrowNotAllowedError(
    188        "Call was blocked due to lack of user activation.");
    189  }
    190 
    191  // Step 4. If this is a select element, and this is not being rendered, then
    192  // throw a "NotSupportedError" DOMException.
    193 
    194  // Flush frames so that IsRendered returns up-to-date results.
    195  (void)GetPrimaryFrame(FlushType::Frames);
    196  if (!IsRendered()) {
    197    return aRv.ThrowNotSupportedError("This select isn't being rendered.");
    198  }
    199 
    200  // Step 5. Show the picker, if applicable, for this.
    201  // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
    202  // To show the picker, if applicable for an input element element:
    203  // We already checked if mutable and user activation earlier, so skip 1 & 2.
    204 
    205  // Step 3. Consume user activation given element's relevant global object.
    206  OwnerDoc()->ConsumeTransientUserGestureActivation();
    207 
    208  // Step 5. Otherwise, the user agent should show any relevant user interface
    209  // for selecting a value for element, in the way it normally would when the
    210  // user interacts with the control.
    211 #if !defined(ANDROID)
    212  if (!IsCombobox()) {
    213    return;
    214  }
    215 #endif
    216 
    217  if (!IsInActiveTab(OwnerDoc())) {
    218    return;
    219  }
    220 
    221  if (!OpenInParentProcess()) {
    222    RefPtr<Document> doc = OwnerDoc();
    223    nsContentUtils::DispatchChromeEvent(doc, this, u"mozshowdropdown"_ns,
    224                                        CanBubble::eYes, Cancelable::eNo);
    225  }
    226 }
    227 
    228 void HTMLSelectElement::GetAutocomplete(DOMString& aValue) {
    229  const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
    230 
    231  mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
    232      attributeVal, aValue, mAutocompleteAttrState);
    233 }
    234 
    235 void HTMLSelectElement::GetAutocompleteInfo(AutocompleteInfo& aInfo) {
    236  const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
    237  mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
    238      attributeVal, aInfo, mAutocompleteInfoState, true);
    239 }
    240 
    241 void HTMLSelectElement::InsertChildBefore(
    242    nsIContent* aKid, nsIContent* aBeforeThis, bool aNotify, ErrorResult& aRv,
    243    nsINode* aOldParent, MutationEffectOnScript aMutationEffectOnScript) {
    244  const uint32_t index =
    245      aBeforeThis ? *ComputeIndexOf(aBeforeThis) : GetChildCount();
    246  SafeOptionListMutation safeMutation(this, this, aKid, index, aNotify);
    247  nsGenericHTMLFormControlElementWithState::InsertChildBefore(
    248      aKid, aBeforeThis, aNotify, aRv, aOldParent, aMutationEffectOnScript);
    249  if (aRv.Failed()) {
    250    safeMutation.MutationFailed();
    251  }
    252 }
    253 
    254 void HTMLSelectElement::RemoveChildNode(
    255    nsIContent* aKid, bool aNotify, const BatchRemovalState* aState,
    256    nsINode* aNewParent, MutationEffectOnScript aMutationEffectOnScript) {
    257  SafeOptionListMutation safeMutation(this, this, nullptr,
    258                                      *ComputeIndexOf(aKid), aNotify);
    259  nsGenericHTMLFormControlElementWithState::RemoveChildNode(
    260      aKid, aNotify, aState, aNewParent, aMutationEffectOnScript);
    261 }
    262 
    263 void HTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions,
    264                                              int32_t aListIndex,
    265                                              int32_t aDepth, bool aNotify) {
    266  MOZ_ASSERT(aDepth == 0 || aDepth == 1);
    267  int32_t insertIndex = aListIndex;
    268 
    269  HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions);
    270  if (optElement) {
    271    mOptions->InsertOptionAt(optElement, insertIndex);
    272    insertIndex++;
    273  } else if (aDepth == 0) {
    274    // If it's at the top level, then we just found out there are non-options
    275    // at the top level, which will throw off the insert count
    276    mNonOptionChildren++;
    277 
    278    // Deal with optgroups
    279    if (aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
    280      mOptGroupCount++;
    281 
    282      for (nsIContent* child = aOptions->GetFirstChild(); child;
    283           child = child->GetNextSibling()) {
    284        optElement = HTMLOptionElement::FromNode(child);
    285        if (optElement) {
    286          mOptions->InsertOptionAt(optElement, insertIndex);
    287          insertIndex++;
    288        }
    289      }
    290    }
    291  }  // else ignore even if optgroup; we want to ignore nested optgroups.
    292 
    293  // Deal with the selected list
    294  if (insertIndex - aListIndex) {
    295    // Fix the currently selected index
    296    if (aListIndex <= mSelectedIndex) {
    297      mSelectedIndex += (insertIndex - aListIndex);
    298      OnSelectionChanged();
    299    }
    300 
    301    // Get the frame stuff for notification. No need to flush here
    302    // since if there's no frame for the select yet the select will
    303    // get into the right state once it's created.
    304    nsISelectControlFrame* selectFrame = nullptr;
    305    AutoWeakFrame weakSelectFrame;
    306    bool didGetFrame = false;
    307 
    308    // Actually select the options if the added options warrant it
    309    for (int32_t i = aListIndex; i < insertIndex; i++) {
    310      // Notify the frame that the option is added
    311      if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
    312        selectFrame = GetSelectFrame();
    313        weakSelectFrame = do_QueryFrame(selectFrame);
    314        didGetFrame = true;
    315      }
    316 
    317      if (selectFrame) {
    318        selectFrame->AddOption(i);
    319      }
    320 
    321      RefPtr<HTMLOptionElement> option = Item(i);
    322      if (option && option->Selected()) {
    323        // Clear all other options
    324        if (!HasAttr(nsGkAtoms::multiple)) {
    325          OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll,
    326                           OptionFlag::SetDisabled, OptionFlag::Notify,
    327                           OptionFlag::InsertingOptions};
    328          SetOptionsSelectedByIndex(i, i, mask);
    329        }
    330 
    331        // This is sort of a hack ... we need to notify that the option was
    332        // set and change selectedIndex even though we didn't really change
    333        // its value.
    334        OnOptionSelected(selectFrame, i, true, false, aNotify);
    335      }
    336    }
    337 
    338    CheckSelectSomething(aNotify);
    339  }
    340 }
    341 
    342 nsresult HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
    343                                                  int32_t aListIndex,
    344                                                  int32_t aDepth,
    345                                                  bool aNotify) {
    346  MOZ_ASSERT(aDepth == 0 || aDepth == 1);
    347  int32_t numRemoved = 0;
    348 
    349  HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions);
    350  if (optElement) {
    351    if (mOptions->ItemAsOption(aListIndex) != optElement) {
    352      NS_ERROR("wrong option at index");
    353      return NS_ERROR_UNEXPECTED;
    354    }
    355    mOptions->RemoveOptionAt(aListIndex);
    356    numRemoved++;
    357  } else if (aDepth == 0) {
    358    // Yay, one less artifact at the top level.
    359    mNonOptionChildren--;
    360 
    361    // Recurse down deeper for options
    362    if (mOptGroupCount && aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
    363      mOptGroupCount--;
    364 
    365      for (nsIContent* child = aOptions->GetFirstChild(); child;
    366           child = child->GetNextSibling()) {
    367        optElement = HTMLOptionElement::FromNode(child);
    368        if (optElement) {
    369          if (mOptions->ItemAsOption(aListIndex) != optElement) {
    370            NS_ERROR("wrong option at index");
    371            return NS_ERROR_UNEXPECTED;
    372          }
    373          mOptions->RemoveOptionAt(aListIndex);
    374          numRemoved++;
    375        }
    376      }
    377    }
    378  }  // else don't check for an optgroup; we want to ignore nested optgroups
    379 
    380  if (numRemoved) {
    381    // Tell the widget we removed the options
    382    nsISelectControlFrame* selectFrame = GetSelectFrame();
    383    if (selectFrame) {
    384      nsAutoScriptBlocker scriptBlocker;
    385      for (int32_t i = aListIndex; i < aListIndex + numRemoved; ++i) {
    386        selectFrame->RemoveOption(i);
    387      }
    388    }
    389 
    390    // Fix the selected index
    391    if (aListIndex <= mSelectedIndex) {
    392      if (mSelectedIndex < (aListIndex + numRemoved)) {
    393        // aListIndex <= mSelectedIndex < aListIndex+numRemoved
    394        // Find a new selected index if it was one of the ones removed.
    395        // If this is a Combobox, no other Item will be selected.
    396        if (IsCombobox()) {
    397          mSelectedIndex = -1;
    398          OnSelectionChanged();
    399        } else {
    400          FindSelectedIndex(aListIndex, aNotify);
    401        }
    402      } else {
    403        // Shift the selected index if something in front of it was removed
    404        // aListIndex+numRemoved <= mSelectedIndex
    405        mSelectedIndex -= numRemoved;
    406        OnSelectionChanged();
    407      }
    408    }
    409 
    410    // Select something in case we removed the selected option on a
    411    // single select
    412    if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) {
    413      // Update the validity state in case of we've just removed the last
    414      // option.
    415      UpdateValueMissingValidityState();
    416      UpdateValidityElementStates(aNotify);
    417    }
    418  }
    419 
    420  return NS_OK;
    421 }
    422 
    423 // XXXldb Doing the processing before the content nodes have been added
    424 // to the document (as the name of this function seems to require, and
    425 // as the callers do), is highly unusual.  Passing around unparented
    426 // content to other parts of the app can make those things think the
    427 // options are the root content node.
    428 NS_IMETHODIMP
    429 HTMLSelectElement::WillAddOptions(nsIContent* aOptions, nsIContent* aParent,
    430                                  int32_t aContentIndex, bool aNotify) {
    431  if (this != aParent && this != aParent->GetParent()) {
    432    return NS_OK;
    433  }
    434  int32_t level = aParent == this ? 0 : 1;
    435 
    436  // Get the index where the options will be inserted
    437  int32_t ind = -1;
    438  if (!mNonOptionChildren) {
    439    // If there are no artifacts, aContentIndex == ind
    440    ind = aContentIndex;
    441  } else {
    442    // If there are artifacts, we have to get the index of the option the
    443    // hard way
    444    int32_t children = aParent->GetChildCount();
    445 
    446    if (aContentIndex >= children) {
    447      // If the content insert is after the end of the parent, then we want to
    448      // get the next index *after* the parent and insert there.
    449      ind = GetOptionIndexAfter(aParent);
    450    } else {
    451      // If the content insert is somewhere in the middle of the container, then
    452      // we want to get the option currently at the index and insert in front of
    453      // that.
    454      nsIContent* currentKid = aParent->GetChildAt_Deprecated(aContentIndex);
    455      NS_ASSERTION(currentKid, "Child not found!");
    456      if (currentKid) {
    457        ind = GetOptionIndexAt(currentKid);
    458      } else {
    459        ind = -1;
    460      }
    461    }
    462  }
    463 
    464  InsertOptionsIntoList(aOptions, ind, level, aNotify);
    465  return NS_OK;
    466 }
    467 
    468 NS_IMETHODIMP
    469 HTMLSelectElement::WillRemoveOptions(nsIContent* aParent, int32_t aContentIndex,
    470                                     bool aNotify) {
    471  if (this != aParent && this != aParent->GetParent()) {
    472    return NS_OK;
    473  }
    474  int32_t level = this == aParent ? 0 : 1;
    475 
    476  // Get the index where the options will be removed
    477  nsIContent* currentKid = aParent->GetChildAt_Deprecated(aContentIndex);
    478  if (currentKid) {
    479    int32_t ind;
    480    if (!mNonOptionChildren) {
    481      // If there are no artifacts, aContentIndex == ind
    482      ind = aContentIndex;
    483    } else {
    484      // If there are artifacts, we have to get the index of the option the
    485      // hard way
    486      ind = GetFirstOptionIndex(currentKid);
    487    }
    488    if (ind != -1) {
    489      nsresult rv = RemoveOptionsFromList(currentKid, ind, level, aNotify);
    490      NS_ENSURE_SUCCESS(rv, rv);
    491    }
    492  }
    493 
    494  return NS_OK;
    495 }
    496 
    497 int32_t HTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions) {
    498  // Search this node and below.
    499  // If not found, find the first one *after* this node.
    500  int32_t retval = GetFirstOptionIndex(aOptions);
    501  if (retval == -1) {
    502    retval = GetOptionIndexAfter(aOptions);
    503  }
    504 
    505  return retval;
    506 }
    507 
    508 int32_t HTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions) {
    509  // - If this is the select, the next option is the last.
    510  // - If not, search all the options after aOptions and up to the last option
    511  //   in the parent.
    512  // - If it's not there, search for the first option after the parent.
    513  if (aOptions == this) {
    514    return Length();
    515  }
    516 
    517  int32_t retval = -1;
    518 
    519  nsCOMPtr<nsIContent> parent = aOptions->GetParent();
    520 
    521  if (parent) {
    522    const int32_t index = parent->ComputeIndexOf_Deprecated(aOptions);
    523    const int32_t count = static_cast<int32_t>(parent->GetChildCount());
    524 
    525    retval = GetFirstChildOptionIndex(parent, index + 1, count);
    526 
    527    if (retval == -1) {
    528      retval = GetOptionIndexAfter(parent);
    529    }
    530  }
    531 
    532  return retval;
    533 }
    534 
    535 int32_t HTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions) {
    536  int32_t listIndex = -1;
    537  HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions);
    538  if (optElement) {
    539    mOptions->GetOptionIndex(optElement, 0, true, &listIndex);
    540    return listIndex;
    541  }
    542 
    543  listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount());
    544 
    545  return listIndex;
    546 }
    547 
    548 int32_t HTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions,
    549                                                    int32_t aStartIndex,
    550                                                    int32_t aEndIndex) {
    551  int32_t retval = -1;
    552 
    553  for (int32_t i = aStartIndex; i < aEndIndex; ++i) {
    554    retval = GetFirstOptionIndex(aOptions->GetChildAt_Deprecated(i));
    555    if (retval != -1) {
    556      break;
    557    }
    558  }
    559 
    560  return retval;
    561 }
    562 
    563 nsISelectControlFrame* HTMLSelectElement::GetSelectFrame() {
    564  return do_QueryFrame(GetPrimaryFrame());
    565 }
    566 
    567 void HTMLSelectElement::Add(
    568    const HTMLOptionElementOrHTMLOptGroupElement& aElement,
    569    const Nullable<HTMLElementOrLong>& aBefore, ErrorResult& aRv) {
    570  nsGenericHTMLElement& element =
    571      aElement.IsHTMLOptionElement() ? static_cast<nsGenericHTMLElement&>(
    572                                           aElement.GetAsHTMLOptionElement())
    573                                     : static_cast<nsGenericHTMLElement&>(
    574                                           aElement.GetAsHTMLOptGroupElement());
    575 
    576  if (aBefore.IsNull()) {
    577    Add(element, static_cast<nsGenericHTMLElement*>(nullptr), aRv);
    578  } else if (aBefore.Value().IsHTMLElement()) {
    579    Add(element, &aBefore.Value().GetAsHTMLElement(), aRv);
    580  } else {
    581    Add(element, aBefore.Value().GetAsLong(), aRv);
    582  }
    583 }
    584 
    585 void HTMLSelectElement::Add(nsGenericHTMLElement& aElement,
    586                            nsGenericHTMLElement* aBefore,
    587                            ErrorResult& aError) {
    588  if (!aBefore) {
    589    Element::AppendChild(aElement, aError);
    590    return;
    591  }
    592 
    593  // Just in case we're not the parent, get the parent of the reference
    594  // element
    595  nsCOMPtr<nsINode> parent = aBefore->Element::GetParentNode();
    596  if (!parent || !parent->IsInclusiveDescendantOf(this)) {
    597    // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
    598    // element.
    599    aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
    600    return;
    601  }
    602 
    603  // If the before parameter is not null, we are equivalent to the
    604  // insertBefore method on the parent of before.
    605  nsCOMPtr<nsINode> refNode = aBefore;
    606  parent->InsertBefore(aElement, refNode, aError);
    607 }
    608 
    609 void HTMLSelectElement::Remove(int32_t aIndex) const {
    610  if (aIndex < 0) {
    611    return;
    612  }
    613 
    614  nsCOMPtr<nsINode> option = Item(static_cast<uint32_t>(aIndex));
    615  if (!option) {
    616    return;
    617  }
    618 
    619  option->Remove();
    620 }
    621 
    622 void HTMLSelectElement::GetType(nsAString& aType) {
    623  if (HasAttr(nsGkAtoms::multiple)) {
    624    aType.AssignLiteral("select-multiple");
    625  } else {
    626    aType.AssignLiteral("select-one");
    627  }
    628 }
    629 
    630 void HTMLSelectElement::SetLength(uint32_t aLength, ErrorResult& aRv) {
    631  constexpr uint32_t kMaxDynamicSelectLength = 100000;
    632 
    633  uint32_t curlen = Length();
    634 
    635  if (curlen > aLength) {  // Remove extra options
    636    for (uint32_t i = curlen; i > aLength; --i) {
    637      Remove(i - 1);
    638    }
    639  } else if (aLength > curlen) {
    640    if (aLength > kMaxDynamicSelectLength) {
    641      nsAutoString strOptionsLength;
    642      strOptionsLength.AppendInt(aLength);
    643 
    644      nsAutoString strLimit;
    645      strLimit.AppendInt(kMaxDynamicSelectLength);
    646 
    647      nsContentUtils::ReportToConsole(
    648          nsIScriptError::warningFlag, "DOM"_ns, OwnerDoc(),
    649          nsContentUtils::eDOM_PROPERTIES,
    650          "SelectOptionsLengthAssignmentWarning", {strOptionsLength, strLimit});
    651      return;
    652    }
    653 
    654    RefPtr<mozilla::dom::NodeInfo> nodeInfo;
    655 
    656    nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::option,
    657                                 getter_AddRefs(nodeInfo));
    658 
    659    nsCOMPtr<nsINode> node = NS_NewHTMLOptionElement(nodeInfo.forget());
    660    for (uint32_t i = curlen; i < aLength; i++) {
    661      nsINode::AppendChild(*node, aRv);
    662      if (aRv.Failed()) {
    663        return;
    664      }
    665 
    666      if (i + 1 < aLength) {
    667        node = node->CloneNode(true, aRv);
    668        if (aRv.Failed()) {
    669          return;
    670        }
    671        MOZ_ASSERT(node);
    672      }
    673    }
    674  }
    675 }
    676 
    677 /* static */
    678 bool HTMLSelectElement::MatchSelectedOptions(Element* aElement,
    679                                             int32_t /* unused */,
    680                                             nsAtom* /* unused */,
    681                                             void* /* unused*/) {
    682  HTMLOptionElement* option = HTMLOptionElement::FromNode(aElement);
    683  return option && option->Selected();
    684 }
    685 
    686 nsIHTMLCollection* HTMLSelectElement::SelectedOptions() {
    687  if (!mSelectedOptions) {
    688    mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr,
    689                                         nullptr, /* deep */ true);
    690  }
    691  return mSelectedOptions;
    692 }
    693 
    694 void HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify) {
    695  int32_t oldSelectedIndex = mSelectedIndex;
    696  OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll,
    697                   OptionFlag::SetDisabled};
    698  if (aNotify) {
    699    mask += OptionFlag::Notify;
    700  }
    701 
    702  SetOptionsSelectedByIndex(aIndex, aIndex, mask);
    703 
    704  nsISelectControlFrame* selectFrame = GetSelectFrame();
    705  if (selectFrame) {
    706    selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
    707  }
    708 
    709  OnSelectionChanged();
    710 }
    711 
    712 bool HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex) const {
    713  HTMLOptionElement* option = Item(static_cast<uint32_t>(aIndex));
    714  return option && option->Selected();
    715 }
    716 
    717 void HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
    718                                         int32_t aIndex, bool aSelected,
    719                                         bool aChangeOptionState,
    720                                         bool aNotify) {
    721  // Set the selected index
    722  if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) {
    723    mSelectedIndex = aIndex;
    724    OnSelectionChanged();
    725  } else if (!aSelected && aIndex == mSelectedIndex) {
    726    FindSelectedIndex(aIndex + 1, aNotify);
    727  }
    728 
    729  if (aChangeOptionState) {
    730    // Tell the option to get its bad self selected
    731    RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(aIndex));
    732    if (option) {
    733      option->SetSelectedInternal(aSelected, aNotify);
    734    }
    735  }
    736 
    737  // Let the frame know too
    738  if (aSelectFrame) {
    739    aSelectFrame->OnOptionSelected(aIndex, aSelected);
    740  }
    741 
    742  UpdateSelectedOptions();
    743  UpdateValueMissingValidityState();
    744  UpdateValidityElementStates(aNotify);
    745 }
    746 
    747 void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) {
    748  mSelectedIndex = -1;
    749  uint32_t len = Length();
    750  for (int32_t i = aStartIndex; i < int32_t(len); i++) {
    751    if (IsOptionSelectedByIndex(i)) {
    752      mSelectedIndex = i;
    753      break;
    754    }
    755  }
    756  OnSelectionChanged();
    757 }
    758 
    759 // XXX Consider splitting this into two functions for ease of reading:
    760 // SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled)
    761 //   startIndex, endIndex - the range of options to turn on
    762 //                          (-1, -1) will clear all indices no matter what.
    763 //   clearAll - will clear all other options unless checkDisabled is on
    764 //              and all the options attempted to be set are disabled
    765 //              (note that if it is not multiple, and an option is selected,
    766 //              everything else will be cleared regardless).
    767 //   checkDisabled - if this is TRUE, and an option is disabled, it will not be
    768 //                   changed regardless of whether it is selected or not.
    769 //                   Generally the UI passes TRUE and JS passes FALSE.
    770 //                   (setDisabled currently is the opposite)
    771 // DeselectOptionsByIndex(startIndex, endIndex, checkDisabled)
    772 //   startIndex, endIndex - the range of options to turn on
    773 //                          (-1, -1) will clear all indices no matter what.
    774 //   checkDisabled - if this is TRUE, and an option is disabled, it will not be
    775 //                   changed regardless of whether it is selected or not.
    776 //                   Generally the UI passes TRUE and JS passes FALSE.
    777 //                   (setDisabled currently is the opposite)
    778 //
    779 // XXXbz the above comment is pretty confusing.  Maybe we should actually
    780 // document the args to this function too, in addition to documenting what
    781 // things might end up looking like?  In particular, pay attention to the
    782 // setDisabled vs checkDisabled business.
    783 bool HTMLSelectElement::SetOptionsSelectedByIndex(int32_t aStartIndex,
    784                                                  int32_t aEndIndex,
    785                                                  OptionFlags aOptionsMask) {
    786 #if 0
    787  printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex,
    788                                      (aOptionsMask.contains(OptionFlag::IsSelected) ? 'Y' : 'N'),
    789                                      (aOptionsMask.contains(OptionFlag::ClearAll) ? 'Y' : 'N'));
    790 #endif
    791  // Don't bother if the select is disabled
    792  if (!aOptionsMask.contains(OptionFlag::SetDisabled) && IsDisabled()) {
    793    return false;
    794  }
    795 
    796  // Don't bother if there are no options
    797  uint32_t numItems = Length();
    798  if (numItems == 0) {
    799    return false;
    800  }
    801 
    802  // First, find out whether multiple items can be selected
    803  bool isMultiple = Multiple();
    804 
    805  // These variables tell us whether any options were selected
    806  // or deselected.
    807  bool optionsSelected = false;
    808  bool optionsDeselected = false;
    809 
    810  nsISelectControlFrame* selectFrame = nullptr;
    811  bool didGetFrame = false;
    812  AutoWeakFrame weakSelectFrame;
    813 
    814  if (aOptionsMask.contains(OptionFlag::IsSelected)) {
    815    // Setting selectedIndex to an out-of-bounds index means -1. (HTML5)
    816    if (aStartIndex < 0 || AssertedCast<uint32_t>(aStartIndex) >= numItems ||
    817        aEndIndex < 0 || AssertedCast<uint32_t>(aEndIndex) >= numItems) {
    818      aStartIndex = -1;
    819      aEndIndex = -1;
    820    }
    821 
    822    // Only select the first value if it's not multiple
    823    if (!isMultiple) {
    824      aEndIndex = aStartIndex;
    825    }
    826 
    827    // This variable tells whether or not all of the options we attempted to
    828    // select are disabled.  If ClearAll is passed in as true, and we do not
    829    // select anything because the options are disabled, we will not clear the
    830    // other options.  (This is to make the UI work the way one might expect.)
    831    bool allDisabled = !aOptionsMask.contains(OptionFlag::SetDisabled);
    832 
    833    //
    834    // Save a little time when clearing other options
    835    //
    836    int32_t previousSelectedIndex = mSelectedIndex;
    837 
    838    //
    839    // Select the requested indices
    840    //
    841    // If index is -1, everything will be deselected (bug 28143)
    842    if (aStartIndex != -1) {
    843      MOZ_ASSERT(aStartIndex >= 0);
    844      MOZ_ASSERT(aEndIndex >= 0);
    845      // Loop through the options and select them (if they are not disabled and
    846      // if they are not already selected).
    847      for (uint32_t optIndex = AssertedCast<uint32_t>(aStartIndex);
    848           optIndex <= AssertedCast<uint32_t>(aEndIndex); optIndex++) {
    849        RefPtr<HTMLOptionElement> option = Item(optIndex);
    850 
    851        // Ignore disabled options.
    852        if (!aOptionsMask.contains(OptionFlag::SetDisabled)) {
    853          if (option && IsOptionDisabled(option)) {
    854            continue;
    855          }
    856          allDisabled = false;
    857        }
    858 
    859        // If the index is already selected, ignore it. On the other hand when
    860        // the option has just been inserted we have to get in sync with it.
    861        if (option && (aOptionsMask.contains(OptionFlag::InsertingOptions) ||
    862                       !option->Selected())) {
    863          // To notify the frame if anything gets changed. No need
    864          // to flush here, if there's no frame yet we don't need to
    865          // force it to be created just to notify it about a change
    866          // in the select.
    867          selectFrame = GetSelectFrame();
    868          weakSelectFrame = do_QueryFrame(selectFrame);
    869          didGetFrame = true;
    870 
    871          OnOptionSelected(selectFrame, optIndex, true, !option->Selected(),
    872                           aOptionsMask.contains(OptionFlag::Notify));
    873          optionsSelected = true;
    874        }
    875      }
    876    }
    877 
    878    // Next remove all other options if single select or all is clear
    879    // If index is -1, everything will be deselected (bug 28143)
    880    if (((!isMultiple && optionsSelected) ||
    881         (aOptionsMask.contains(OptionFlag::ClearAll) && !allDisabled) ||
    882         aStartIndex == -1) &&
    883        previousSelectedIndex != -1) {
    884      for (uint32_t optIndex = AssertedCast<uint32_t>(previousSelectedIndex);
    885           optIndex < numItems; optIndex++) {
    886        if (static_cast<int32_t>(optIndex) < aStartIndex ||
    887            static_cast<int32_t>(optIndex) > aEndIndex) {
    888          HTMLOptionElement* option = Item(optIndex);
    889          // If the index is already deselected, ignore it.
    890          if (option && option->Selected()) {
    891            if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
    892              // To notify the frame if anything gets changed, don't
    893              // flush, if the frame doesn't exist we don't need to
    894              // create it just to tell it about this change.
    895              selectFrame = GetSelectFrame();
    896              weakSelectFrame = do_QueryFrame(selectFrame);
    897 
    898              didGetFrame = true;
    899            }
    900 
    901            OnOptionSelected(selectFrame, optIndex, false, true,
    902                             aOptionsMask.contains(OptionFlag::Notify));
    903            optionsDeselected = true;
    904 
    905            // Only need to deselect one option if not multiple
    906            if (!isMultiple) {
    907              break;
    908            }
    909          }
    910        }
    911      }
    912    }
    913  } else {
    914    // If we're deselecting, loop through all selected items and deselect
    915    // any that are in the specified range.
    916    for (int32_t optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
    917      HTMLOptionElement* option = Item(optIndex);
    918      if (!aOptionsMask.contains(OptionFlag::SetDisabled) &&
    919          IsOptionDisabled(option)) {
    920        continue;
    921      }
    922 
    923      // If the index is already selected, ignore it.
    924      if (option && option->Selected()) {
    925        if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
    926          // To notify the frame if anything gets changed, don't
    927          // flush, if the frame doesn't exist we don't need to
    928          // create it just to tell it about this change.
    929          selectFrame = GetSelectFrame();
    930          weakSelectFrame = do_QueryFrame(selectFrame);
    931 
    932          didGetFrame = true;
    933        }
    934 
    935        OnOptionSelected(selectFrame, optIndex, false, true,
    936                         aOptionsMask.contains(OptionFlag::Notify));
    937        optionsDeselected = true;
    938      }
    939    }
    940  }
    941 
    942  // Make sure something is selected unless we were set to -1 (none)
    943  if (optionsDeselected && aStartIndex != -1 &&
    944      !aOptionsMask.contains(OptionFlag::NoReselect)) {
    945    optionsSelected =
    946        CheckSelectSomething(aOptionsMask.contains(OptionFlag::Notify)) ||
    947        optionsSelected;
    948  }
    949 
    950  // Let the caller know whether anything was changed
    951  return optionsSelected || optionsDeselected;
    952 }
    953 
    954 NS_IMETHODIMP
    955 HTMLSelectElement::IsOptionDisabled(int32_t aIndex, bool* aIsDisabled) {
    956  *aIsDisabled = false;
    957  RefPtr<HTMLOptionElement> option = Item(aIndex);
    958  NS_ENSURE_TRUE(option, NS_ERROR_FAILURE);
    959 
    960  *aIsDisabled = IsOptionDisabled(option);
    961  return NS_OK;
    962 }
    963 
    964 bool HTMLSelectElement::IsOptionDisabled(HTMLOptionElement* aOption) const {
    965  MOZ_ASSERT(aOption);
    966  if (aOption->Disabled()) {
    967    return true;
    968  }
    969 
    970  // Check for disabled optgroups
    971  // If there are no artifacts, there are no optgroups
    972  if (mNonOptionChildren) {
    973    for (nsCOMPtr<Element> node =
    974             static_cast<nsINode*>(aOption)->GetParentElement();
    975         node; node = node->GetParentElement()) {
    976      // If we reached the select element, we're done
    977      if (node->IsHTMLElement(nsGkAtoms::select)) {
    978        return false;
    979      }
    980 
    981      RefPtr<HTMLOptGroupElement> optGroupElement =
    982          HTMLOptGroupElement::FromNode(node);
    983 
    984      if (!optGroupElement) {
    985        // If you put something else between you and the optgroup, you're a
    986        // moron and you deserve not to have optgroup disabling work.
    987        return false;
    988      }
    989 
    990      if (optGroupElement->Disabled()) {
    991        return true;
    992      }
    993    }
    994  }
    995 
    996  return false;
    997 }
    998 
    999 void HTMLSelectElement::GetValue(DOMString& aValue) const {
   1000  int32_t selectedIndex = SelectedIndex();
   1001  if (selectedIndex < 0) {
   1002    return;
   1003  }
   1004 
   1005  RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(selectedIndex));
   1006 
   1007  if (!option) {
   1008    return;
   1009  }
   1010 
   1011  option->GetValue(aValue);
   1012 }
   1013 
   1014 void HTMLSelectElement::SetValue(const nsAString& aValue) {
   1015  uint32_t length = Length();
   1016 
   1017  for (uint32_t i = 0; i < length; i++) {
   1018    RefPtr<HTMLOptionElement> option = Item(i);
   1019    if (!option) {
   1020      continue;
   1021    }
   1022 
   1023    nsAutoString optionVal;
   1024    option->GetValue(optionVal);
   1025    if (optionVal.Equals(aValue)) {
   1026      SetSelectedIndexInternal(int32_t(i), true);
   1027      return;
   1028    }
   1029  }
   1030  // No matching option was found.
   1031  SetSelectedIndexInternal(-1, true);
   1032 }
   1033 
   1034 int32_t HTMLSelectElement::TabIndexDefault() { return 0; }
   1035 
   1036 bool HTMLSelectElement::IsHTMLFocusable(IsFocusableFlags aFlags,
   1037                                        bool* aIsFocusable,
   1038                                        int32_t* aTabIndex) {
   1039  if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
   1040          aFlags, aIsFocusable, aTabIndex)) {
   1041    return true;
   1042  }
   1043 
   1044  *aIsFocusable = !IsDisabled();
   1045 
   1046  return false;
   1047 }
   1048 
   1049 bool HTMLSelectElement::CheckSelectSomething(bool aNotify) {
   1050  if (mIsDoneAddingChildren) {
   1051    if (mSelectedIndex < 0 && IsCombobox()) {
   1052      return SelectSomething(aNotify);
   1053    }
   1054  }
   1055  return false;
   1056 }
   1057 
   1058 bool HTMLSelectElement::SelectSomething(bool aNotify) {
   1059  // If we're not done building the select, don't play with this yet.
   1060  if (!mIsDoneAddingChildren) {
   1061    return false;
   1062  }
   1063 
   1064  uint32_t count = Length();
   1065  for (uint32_t i = 0; i < count; i++) {
   1066    bool disabled;
   1067    nsresult rv = IsOptionDisabled(i, &disabled);
   1068 
   1069    if (NS_FAILED(rv) || !disabled) {
   1070      SetSelectedIndexInternal(i, aNotify);
   1071 
   1072      UpdateValueMissingValidityState();
   1073      UpdateValidityElementStates(aNotify);
   1074 
   1075      return true;
   1076    }
   1077  }
   1078 
   1079  return false;
   1080 }
   1081 
   1082 nsresult HTMLSelectElement::BindToTree(BindContext& aContext,
   1083                                       nsINode& aParent) {
   1084  nsresult rv =
   1085      nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
   1086  NS_ENSURE_SUCCESS(rv, rv);
   1087 
   1088  // If there is a disabled fieldset in the parent chain, the element is now
   1089  // barred from constraint validation.
   1090  // XXXbz is this still needed now that fieldset changes always call
   1091  // FieldSetDisabledChanged?
   1092  UpdateBarredFromConstraintValidation();
   1093 
   1094  // And now make sure our state is up to date
   1095  UpdateValidityElementStates(false);
   1096 
   1097  return rv;
   1098 }
   1099 
   1100 void HTMLSelectElement::UnbindFromTree(UnbindContext& aContext) {
   1101  nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext);
   1102 
   1103  // We might be no longer disabled because our parent chain changed.
   1104  // XXXbz is this still needed now that fieldset changes always call
   1105  // FieldSetDisabledChanged?
   1106  UpdateBarredFromConstraintValidation();
   1107 
   1108  // And now make sure our state is up to date
   1109  UpdateValidityElementStates(false);
   1110 }
   1111 
   1112 void HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
   1113                                      const nsAttrValue* aValue, bool aNotify) {
   1114  if (aNameSpaceID == kNameSpaceID_None) {
   1115    if (aName == nsGkAtoms::disabled) {
   1116      if (aNotify) {
   1117        mDisabledChanged = true;
   1118      }
   1119    } else if (aName == nsGkAtoms::multiple) {
   1120      if (!aValue && aNotify && mSelectedIndex >= 0) {
   1121        // We're changing from being a multi-select to a single-select.
   1122        // Make sure we only have one option selected before we do that.
   1123        // Note that this needs to come before we really unset the attr,
   1124        // since SetOptionsSelectedByIndex does some bail-out type
   1125        // optimization for cases when the select is not multiple that
   1126        // would lead to only a single option getting deselected.
   1127        SetSelectedIndexInternal(mSelectedIndex, aNotify);
   1128      }
   1129    }
   1130  }
   1131 
   1132  return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
   1133      aNameSpaceID, aName, aValue, aNotify);
   1134 }
   1135 
   1136 void HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
   1137                                     const nsAttrValue* aValue,
   1138                                     const nsAttrValue* aOldValue,
   1139                                     nsIPrincipal* aSubjectPrincipal,
   1140                                     bool aNotify) {
   1141  if (aNameSpaceID == kNameSpaceID_None) {
   1142    if (aName == nsGkAtoms::disabled) {
   1143      // This *has* to be called *before* validity state check because
   1144      // UpdateBarredFromConstraintValidation and
   1145      // UpdateValueMissingValidityState depend on our disabled state.
   1146      UpdateDisabledState(aNotify);
   1147 
   1148      UpdateValueMissingValidityState();
   1149      UpdateBarredFromConstraintValidation();
   1150      UpdateValidityElementStates(aNotify);
   1151    } else if (aName == nsGkAtoms::required) {
   1152      // This *has* to be called *before* UpdateValueMissingValidityState
   1153      // because UpdateValueMissingValidityState depends on our required
   1154      // state.
   1155      UpdateRequiredState(!!aValue, aNotify);
   1156      UpdateValueMissingValidityState();
   1157      UpdateValidityElementStates(aNotify);
   1158    } else if (aName == nsGkAtoms::autocomplete) {
   1159      // Clear the cached @autocomplete attribute and autocompleteInfo state.
   1160      mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
   1161      mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
   1162    } else if (aName == nsGkAtoms::multiple) {
   1163      if (!aValue && aNotify) {
   1164        // We might have become a combobox; make sure _something_ gets
   1165        // selected in that case
   1166        CheckSelectSomething(aNotify);
   1167      }
   1168    }
   1169  }
   1170 
   1171  return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
   1172      aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
   1173 }
   1174 
   1175 void HTMLSelectElement::DoneAddingChildren(bool aHaveNotified) {
   1176  mIsDoneAddingChildren = true;
   1177 
   1178  nsISelectControlFrame* selectFrame = GetSelectFrame();
   1179 
   1180  // If we foolishly tried to restore before we were done adding
   1181  // content, restore the rest of the options proper-like
   1182  if (mRestoreState) {
   1183    RestoreStateTo(*mRestoreState);
   1184    mRestoreState = nullptr;
   1185  }
   1186 
   1187  // Notify the frame
   1188  if (selectFrame) {
   1189    selectFrame->DoneAddingChildren(true);
   1190  }
   1191 
   1192  if (!mInhibitStateRestoration) {
   1193    GenerateStateKey();
   1194    RestoreFormControlState();
   1195  }
   1196 
   1197  // Now that we're done, select something (if it's a single select something
   1198  // must be selected)
   1199  if (!CheckSelectSomething(false)) {
   1200    // If an option has @selected set, it will be selected during parsing but
   1201    // with an empty value. We have to make sure the select element updates it's
   1202    // validity state to take this into account.
   1203    UpdateValueMissingValidityState();
   1204 
   1205    // And now make sure we update our content state too
   1206    UpdateValidityElementStates(aHaveNotified);
   1207  }
   1208 
   1209  mDefaultSelectionSet = true;
   1210 }
   1211 
   1212 bool HTMLSelectElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
   1213                                       const nsAString& aValue,
   1214                                       nsIPrincipal* aMaybeScriptedPrincipal,
   1215                                       nsAttrValue& aResult) {
   1216  if (kNameSpaceID_None == aNamespaceID) {
   1217    if (aAttribute == nsGkAtoms::size) {
   1218      return aResult.ParsePositiveIntValue(aValue);
   1219    }
   1220    if (aAttribute == nsGkAtoms::autocomplete) {
   1221      aResult.ParseAtomArray(aValue);
   1222      return true;
   1223    }
   1224  }
   1225  return nsGenericHTMLFormControlElementWithState::ParseAttribute(
   1226      aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
   1227 }
   1228 
   1229 void HTMLSelectElement::MapAttributesIntoRule(
   1230    MappedDeclarationsBuilder& aBuilder) {
   1231  nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
   1232      aBuilder);
   1233  nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder);
   1234 }
   1235 
   1236 nsChangeHint HTMLSelectElement::GetAttributeChangeHint(
   1237    const nsAtom* aAttribute, AttrModType aModType) const {
   1238  nsChangeHint retval =
   1239      nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
   1240          aAttribute, aModType);
   1241  if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) {
   1242    retval |= nsChangeHint_ReconstructFrame;
   1243  }
   1244  return retval;
   1245 }
   1246 
   1247 NS_IMETHODIMP_(bool)
   1248 HTMLSelectElement::IsAttributeMapped(const nsAtom* aAttribute) const {
   1249  static const MappedAttributeEntry* const map[] = {sCommonAttributeMap,
   1250                                                    sImageAlignAttributeMap};
   1251 
   1252  return FindAttributeDependence(aAttribute, map);
   1253 }
   1254 
   1255 nsMapRuleToAttributesFunc HTMLSelectElement::GetAttributeMappingFunction()
   1256    const {
   1257  return &MapAttributesIntoRule;
   1258 }
   1259 
   1260 bool HTMLSelectElement::IsDisabledForEvents(WidgetEvent* aEvent) {
   1261  return IsElementDisabledForEvents(aEvent, GetPrimaryFrame());
   1262 }
   1263 
   1264 void HTMLSelectElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
   1265  aVisitor.mCanHandle = false;
   1266  if (IsDisabledForEvents(aVisitor.mEvent)) {
   1267    return;
   1268  }
   1269 
   1270  nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor);
   1271 }
   1272 
   1273 void HTMLSelectElement::UpdateValidityElementStates(bool aNotify) {
   1274  AutoStateChangeNotifier notifier(*this, aNotify);
   1275  RemoveStatesSilently(ElementState::VALIDITY_STATES);
   1276  if (!IsCandidateForConstraintValidation()) {
   1277    return;
   1278  }
   1279 
   1280  ElementState state;
   1281  if (IsValid()) {
   1282    state |= ElementState::VALID;
   1283    if (mUserInteracted) {
   1284      state |= ElementState::USER_VALID;
   1285    }
   1286  } else {
   1287    state |= ElementState::INVALID;
   1288    if (mUserInteracted) {
   1289      state |= ElementState::USER_INVALID;
   1290    }
   1291  }
   1292 
   1293  AddStatesSilently(state);
   1294 }
   1295 
   1296 void HTMLSelectElement::SaveState() {
   1297  PresState* presState = GetPrimaryPresState();
   1298  if (!presState) {
   1299    return;
   1300  }
   1301 
   1302  SelectContentData state;
   1303 
   1304  uint32_t len = Length();
   1305 
   1306  for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
   1307    HTMLOptionElement* option = Item(optIndex);
   1308    if (option && option->Selected()) {
   1309      nsAutoString value;
   1310      option->GetValue(value);
   1311      if (value.IsEmpty()) {
   1312        state.indices().AppendElement(optIndex);
   1313      } else {
   1314        state.values().AppendElement(std::move(value));
   1315      }
   1316    }
   1317  }
   1318 
   1319  presState->contentData() = std::move(state);
   1320 
   1321  if (mDisabledChanged) {
   1322    // We do not want to save the real disabled state but the disabled
   1323    // attribute.
   1324    presState->disabled() = HasAttr(nsGkAtoms::disabled);
   1325    presState->disabledSet() = true;
   1326  }
   1327 }
   1328 
   1329 bool HTMLSelectElement::RestoreState(PresState* aState) {
   1330  // Get the presentation state object to retrieve our stuff out of.
   1331  const PresContentData& state = aState->contentData();
   1332  if (state.type() == PresContentData::TSelectContentData) {
   1333    RestoreStateTo(state.get_SelectContentData());
   1334 
   1335    // Don't flush, if the frame doesn't exist yet it doesn't care if
   1336    // we're reset or not.
   1337    DispatchContentReset();
   1338  }
   1339 
   1340  if (aState->disabledSet() && !aState->disabled()) {
   1341    SetDisabled(false, IgnoreErrors());
   1342  }
   1343 
   1344  return false;
   1345 }
   1346 
   1347 void HTMLSelectElement::RestoreStateTo(const SelectContentData& aNewSelected) {
   1348  if (!mIsDoneAddingChildren) {
   1349    // Make a copy of the state for us to restore from in the future.
   1350    mRestoreState = MakeUnique<SelectContentData>(aNewSelected);
   1351    return;
   1352  }
   1353 
   1354  uint32_t len = Length();
   1355  OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll,
   1356                   OptionFlag::SetDisabled, OptionFlag::Notify};
   1357 
   1358  // First clear all
   1359  SetOptionsSelectedByIndex(-1, -1, mask);
   1360 
   1361  // Select by index.
   1362  for (uint32_t idx : aNewSelected.indices()) {
   1363    if (idx < len) {
   1364      SetOptionsSelectedByIndex(idx, idx,
   1365                                {OptionFlag::IsSelected,
   1366                                 OptionFlag::SetDisabled, OptionFlag::Notify});
   1367    }
   1368  }
   1369 
   1370  // Select by value.
   1371  for (uint32_t i = 0; i < len; ++i) {
   1372    HTMLOptionElement* option = Item(i);
   1373    if (option) {
   1374      nsAutoString value;
   1375      option->GetValue(value);
   1376      if (aNewSelected.values().Contains(value)) {
   1377        SetOptionsSelectedByIndex(
   1378            i, i,
   1379            {OptionFlag::IsSelected, OptionFlag::SetDisabled,
   1380             OptionFlag::Notify});
   1381      }
   1382    }
   1383  }
   1384 }
   1385 
   1386 // nsIFormControl
   1387 
   1388 NS_IMETHODIMP
   1389 HTMLSelectElement::Reset() {
   1390  uint32_t numSelected = 0;
   1391 
   1392  //
   1393  // Cycle through the options array and reset the options
   1394  //
   1395  uint32_t numOptions = Length();
   1396 
   1397  for (uint32_t i = 0; i < numOptions; i++) {
   1398    RefPtr<HTMLOptionElement> option = Item(i);
   1399    if (option) {
   1400      //
   1401      // Reset the option to its default value
   1402      //
   1403 
   1404      OptionFlags mask = {OptionFlag::SetDisabled, OptionFlag::Notify,
   1405                          OptionFlag::NoReselect};
   1406      if (option->DefaultSelected()) {
   1407        mask += OptionFlag::IsSelected;
   1408        numSelected++;
   1409      }
   1410 
   1411      SetOptionsSelectedByIndex(i, i, mask);
   1412      option->SetSelectedChanged(false);
   1413    }
   1414  }
   1415 
   1416  //
   1417  // If nothing was selected and it's not multiple, select something
   1418  //
   1419  if (numSelected == 0 && IsCombobox()) {
   1420    SelectSomething(true);
   1421  }
   1422 
   1423  OnSelectionChanged();
   1424  SetUserInteracted(false);
   1425 
   1426  // Let the frame know we were reset
   1427  //
   1428  // Don't flush, if there's no frame yet it won't care about us being
   1429  // reset even if we forced it to be created now.
   1430  //
   1431  DispatchContentReset();
   1432 
   1433  return NS_OK;
   1434 }
   1435 
   1436 NS_IMETHODIMP
   1437 HTMLSelectElement::SubmitNamesValues(FormData* aFormData) {
   1438  //
   1439  // Get the name (if no name, no submit)
   1440  //
   1441  nsAutoString name;
   1442  GetAttr(nsGkAtoms::name, name);
   1443  if (name.IsEmpty()) {
   1444    return NS_OK;
   1445  }
   1446 
   1447  //
   1448  // Submit
   1449  //
   1450  uint32_t len = Length();
   1451 
   1452  for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
   1453    HTMLOptionElement* option = Item(optIndex);
   1454 
   1455    // Don't send disabled options
   1456    if (!option || IsOptionDisabled(option)) {
   1457      continue;
   1458    }
   1459 
   1460    if (!option->Selected()) {
   1461      continue;
   1462    }
   1463 
   1464    nsString value;
   1465    option->GetValue(value);
   1466 
   1467    aFormData->AddNameValuePair(name, value);
   1468  }
   1469 
   1470  return NS_OK;
   1471 }
   1472 
   1473 void HTMLSelectElement::DispatchContentReset() {
   1474  if (nsListControlFrame* listFrame = do_QueryFrame(GetPrimaryFrame())) {
   1475    listFrame->OnContentReset();
   1476  }
   1477 }
   1478 
   1479 static void AddOptions(nsIContent* aRoot, HTMLOptionsCollection* aArray) {
   1480  for (nsIContent* child = aRoot->GetFirstChild(); child;
   1481       child = child->GetNextSibling()) {
   1482    HTMLOptionElement* opt = HTMLOptionElement::FromNode(child);
   1483    if (opt) {
   1484      aArray->AppendOption(opt);
   1485    } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
   1486      for (nsIContent* grandchild = child->GetFirstChild(); grandchild;
   1487           grandchild = grandchild->GetNextSibling()) {
   1488        opt = HTMLOptionElement::FromNode(grandchild);
   1489        if (opt) {
   1490          aArray->AppendOption(opt);
   1491        }
   1492      }
   1493    }
   1494  }
   1495 }
   1496 
   1497 void HTMLSelectElement::RebuildOptionsArray(bool aNotify) {
   1498  mOptions->Clear();
   1499  AddOptions(this, mOptions);
   1500  FindSelectedIndex(0, aNotify);
   1501 }
   1502 
   1503 bool HTMLSelectElement::IsValueMissing() const {
   1504  if (!Required()) {
   1505    return false;
   1506  }
   1507 
   1508  uint32_t length = Length();
   1509 
   1510  for (uint32_t i = 0; i < length; ++i) {
   1511    RefPtr<HTMLOptionElement> option = Item(i);
   1512    // Check for a placeholder label option, don't count it as a valid value.
   1513    if (i == 0 && !Multiple() && Size() <= 1 && option->GetParent() == this) {
   1514      nsAutoString value;
   1515      option->GetValue(value);
   1516      if (value.IsEmpty()) {
   1517        continue;
   1518      }
   1519    }
   1520 
   1521    if (!option->Selected()) {
   1522      continue;
   1523    }
   1524 
   1525    return false;
   1526  }
   1527 
   1528  return true;
   1529 }
   1530 
   1531 void HTMLSelectElement::UpdateValueMissingValidityState() {
   1532  SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
   1533 }
   1534 
   1535 nsresult HTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage,
   1536                                                 ValidityStateType aType) {
   1537  switch (aType) {
   1538    case VALIDITY_STATE_VALUE_MISSING: {
   1539      nsAutoString message;
   1540      nsresult rv = nsContentUtils::GetMaybeLocalizedString(
   1541          nsContentUtils::eDOM_PROPERTIES, "FormValidationSelectMissing",
   1542          OwnerDoc(), message);
   1543      aValidationMessage = message;
   1544      return rv;
   1545    }
   1546    default: {
   1547      return ConstraintValidation::GetValidationMessage(aValidationMessage,
   1548                                                        aType);
   1549    }
   1550  }
   1551 }
   1552 
   1553 #ifdef DEBUG
   1554 
   1555 void HTMLSelectElement::VerifyOptionsArray() {
   1556  int32_t index = 0;
   1557  for (nsIContent* child = nsINode::GetFirstChild(); child;
   1558       child = child->GetNextSibling()) {
   1559    HTMLOptionElement* opt = HTMLOptionElement::FromNode(child);
   1560    if (opt) {
   1561      NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
   1562                   "Options collection broken");
   1563    } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
   1564      for (nsIContent* grandchild = child->GetFirstChild(); grandchild;
   1565           grandchild = grandchild->GetNextSibling()) {
   1566        opt = HTMLOptionElement::FromNode(grandchild);
   1567        if (opt) {
   1568          NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
   1569                       "Options collection broken");
   1570        }
   1571      }
   1572    }
   1573  }
   1574 }
   1575 
   1576 #endif
   1577 
   1578 void HTMLSelectElement::UpdateBarredFromConstraintValidation() {
   1579  SetBarredFromConstraintValidation(
   1580      HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
   1581 }
   1582 
   1583 void HTMLSelectElement::FieldSetDisabledChanged(bool aNotify) {
   1584  // This *has* to be called before UpdateBarredFromConstraintValidation and
   1585  // UpdateValueMissingValidityState because these two functions depend on our
   1586  // disabled state.
   1587  nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
   1588 
   1589  UpdateValueMissingValidityState();
   1590  UpdateBarredFromConstraintValidation();
   1591  UpdateValidityElementStates(aNotify);
   1592 }
   1593 
   1594 void HTMLSelectElement::OnSelectionChanged() {
   1595  if (!mDefaultSelectionSet) {
   1596    return;
   1597  }
   1598 
   1599  if (State().HasState(ElementState::AUTOFILL)) {
   1600    RemoveStates(ElementState::AUTOFILL | ElementState::AUTOFILL_PREVIEW);
   1601  }
   1602 
   1603  UpdateSelectedOptions();
   1604 }
   1605 
   1606 void HTMLSelectElement::UpdateSelectedOptions() {
   1607  if (mSelectedOptions) {
   1608    mSelectedOptions->SetDirty();
   1609  }
   1610 }
   1611 
   1612 void HTMLSelectElement::SetUserInteracted(bool aInteracted) {
   1613  if (mUserInteracted == aInteracted) {
   1614    return;
   1615  }
   1616  mUserInteracted = aInteracted;
   1617  UpdateValidityElementStates(true);
   1618 }
   1619 
   1620 void HTMLSelectElement::SetPreviewValue(const nsAString& aValue) {
   1621  mPreviewValue = aValue;
   1622  nsContentUtils::RemoveNewlines(mPreviewValue);
   1623  nsComboboxControlFrame* comboFrame = do_QueryFrame(GetPrimaryFrame());
   1624  if (comboFrame) {
   1625    comboFrame->RedisplaySelectedText();
   1626  }
   1627 }
   1628 
   1629 void HTMLSelectElement::UserFinishedInteracting(bool aChanged) {
   1630  SetUserInteracted(true);
   1631  if (!aChanged) {
   1632    return;
   1633  }
   1634 
   1635  // Dispatch the input event.
   1636  DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
   1637  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1638                       "Failed to dispatch input event");
   1639 
   1640  // Dispatch the change event.
   1641  nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"change"_ns,
   1642                                       CanBubble::eYes, Cancelable::eNo);
   1643 }
   1644 
   1645 JSObject* HTMLSelectElement::WrapNode(JSContext* aCx,
   1646                                      JS::Handle<JSObject*> aGivenProto) {
   1647  return HTMLSelectElement_Binding::Wrap(aCx, this, aGivenProto);
   1648 }
   1649 
   1650 }  // namespace mozilla::dom