tor-browser

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

HTMLOptionElement.cpp (10728B)


      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/HTMLOptionElement.h"
      8 
      9 #include "HTMLOptGroupElement.h"
     10 #include "mozilla/dom/HTMLOptionElementBinding.h"
     11 #include "mozilla/dom/HTMLSelectElement.h"
     12 #include "nsGkAtoms.h"
     13 #include "nsIFormControl.h"
     14 #include "nsISelectControlFrame.h"
     15 #include "nsStyleConsts.h"
     16 
     17 // Notify/query select frame for selected state
     18 #include "mozAutoDocUpdate.h"
     19 #include "mozilla/dom/Document.h"
     20 #include "nsCOMPtr.h"
     21 #include "nsContentCreatorFunctions.h"
     22 #include "nsNodeInfoManager.h"
     23 #include "nsTextNode.h"
     24 
     25 /**
     26 * Implementation of <option>
     27 */
     28 
     29 NS_IMPL_NS_NEW_HTML_ELEMENT(Option)
     30 
     31 namespace mozilla::dom {
     32 
     33 HTMLOptionElement::HTMLOptionElement(
     34    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     35    : nsGenericHTMLElement(std::move(aNodeInfo)) {
     36  // We start off enabled
     37  AddStatesSilently(ElementState::ENABLED);
     38 }
     39 
     40 HTMLOptionElement::~HTMLOptionElement() = default;
     41 
     42 NS_IMPL_ELEMENT_CLONE(HTMLOptionElement)
     43 
     44 mozilla::dom::HTMLFormElement* HTMLOptionElement::GetForm() {
     45  HTMLSelectElement* selectControl = GetSelect();
     46  return selectControl ? selectControl->GetForm() : nullptr;
     47 }
     48 
     49 void HTMLOptionElement::SetSelectedInternal(bool aValue, bool aNotify) {
     50  mSelectedChanged = true;
     51  SetStates(ElementState::CHECKED, aValue, aNotify);
     52 }
     53 
     54 void HTMLOptionElement::OptGroupDisabledChanged(bool aNotify) {
     55  UpdateDisabledState(aNotify);
     56 }
     57 
     58 void HTMLOptionElement::UpdateDisabledState(bool aNotify) {
     59  bool isDisabled = HasAttr(nsGkAtoms::disabled);
     60 
     61  if (!isDisabled) {
     62    nsIContent* parent = GetParent();
     63    if (auto optGroupElement = HTMLOptGroupElement::FromNodeOrNull(parent)) {
     64      isDisabled = optGroupElement->IsDisabled();
     65    }
     66  }
     67 
     68  ElementState disabledStates;
     69  if (isDisabled) {
     70    disabledStates |= ElementState::DISABLED;
     71  } else {
     72    disabledStates |= ElementState::ENABLED;
     73  }
     74 
     75  ElementState oldDisabledStates = State() & ElementState::DISABLED_STATES;
     76  ElementState changedStates = disabledStates ^ oldDisabledStates;
     77 
     78  if (!changedStates.IsEmpty()) {
     79    ToggleStates(changedStates, aNotify);
     80  }
     81 }
     82 
     83 void HTMLOptionElement::SetSelected(bool aValue) {
     84  // Note: The select content obj maintains all the PresState
     85  // so defer to it to get the answer
     86  HTMLSelectElement* selectInt = GetSelect();
     87  if (selectInt) {
     88    int32_t index = Index();
     89    HTMLSelectElement::OptionFlags mask{
     90        HTMLSelectElement::OptionFlag::SetDisabled,
     91        HTMLSelectElement::OptionFlag::Notify};
     92    if (aValue) {
     93      mask += HTMLSelectElement::OptionFlag::IsSelected;
     94    }
     95 
     96    // This should end up calling SetSelectedInternal
     97    selectInt->SetOptionsSelectedByIndex(index, index, mask);
     98  } else {
     99    SetSelectedInternal(aValue, true);
    100  }
    101 }
    102 
    103 int32_t HTMLOptionElement::Index() {
    104  static int32_t defaultIndex = 0;
    105 
    106  // Only select elements can contain a list of options.
    107  HTMLSelectElement* selectElement = GetSelect();
    108  if (!selectElement) {
    109    return defaultIndex;
    110  }
    111 
    112  HTMLOptionsCollection* options = selectElement->GetOptions();
    113  if (!options) {
    114    return defaultIndex;
    115  }
    116 
    117  int32_t index = defaultIndex;
    118  MOZ_ALWAYS_SUCCEEDS(options->GetOptionIndex(this, 0, true, &index));
    119  return index;
    120 }
    121 
    122 nsChangeHint HTMLOptionElement::GetAttributeChangeHint(
    123    const nsAtom* aAttribute, AttrModType aModType) const {
    124  nsChangeHint retval =
    125      nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
    126 
    127  if (aAttribute == nsGkAtoms::label) {
    128    retval |= nsChangeHint_ReconstructFrame;
    129  } else if (aAttribute == nsGkAtoms::text) {
    130    retval |= NS_STYLE_HINT_REFLOW;
    131  }
    132  return retval;
    133 }
    134 
    135 void HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
    136                                      const nsAttrValue* aValue, bool aNotify) {
    137  nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
    138 
    139  if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected ||
    140      mSelectedChanged) {
    141    return;
    142  }
    143 
    144  // We just changed out selected state (since we look at the "selected"
    145  // attribute when mSelectedChanged is false).  Let's tell our select about
    146  // it.
    147  HTMLSelectElement* selectInt = GetSelect();
    148  if (!selectInt) {
    149    // If option is a child of select, SetOptionsSelectedByIndex will set the
    150    // selected state if needed.
    151    SetStates(ElementState::CHECKED, !!aValue, aNotify);
    152    return;
    153  }
    154 
    155  NS_ASSERTION(!mSelectedChanged, "Shouldn't be here");
    156 
    157  bool inSetDefaultSelected = mIsInSetDefaultSelected;
    158  mIsInSetDefaultSelected = true;
    159 
    160  int32_t index = Index();
    161  HTMLSelectElement::OptionFlags mask =
    162      HTMLSelectElement::OptionFlag::SetDisabled;
    163  if (aValue) {
    164    mask += HTMLSelectElement::OptionFlag::IsSelected;
    165  }
    166 
    167  if (aNotify) {
    168    mask += HTMLSelectElement::OptionFlag::Notify;
    169  }
    170 
    171  // This can end up calling SetSelectedInternal if our selected state needs to
    172  // change, which we will allow to take effect so that parts of
    173  // SetOptionsSelectedByIndex that might depend on it working don't get
    174  // confused.
    175  selectInt->SetOptionsSelectedByIndex(index, index, mask);
    176 
    177  // Now reset our members; when we finish the attr set we'll end up with the
    178  // rigt selected state.
    179  mIsInSetDefaultSelected = inSetDefaultSelected;
    180  // the selected state might have been changed by SetOptionsSelectedByIndex,
    181  // possibly more than once; make sure our mSelectedChanged state is set back
    182  // correctly.
    183  mSelectedChanged = false;
    184 }
    185 
    186 void HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
    187                                     const nsAttrValue* aValue,
    188                                     const nsAttrValue* aOldValue,
    189                                     nsIPrincipal* aSubjectPrincipal,
    190                                     bool aNotify) {
    191  if (aNameSpaceID == kNameSpaceID_None) {
    192    if (aName == nsGkAtoms::disabled) {
    193      UpdateDisabledState(aNotify);
    194    }
    195 
    196    if (aName == nsGkAtoms::value && Selected()) {
    197      // Since this option is selected, changing value may have changed missing
    198      // validity state of the select element
    199      if (HTMLSelectElement* select = GetSelect()) {
    200        select->UpdateValueMissingValidityState();
    201      }
    202    }
    203 
    204    if (aName == nsGkAtoms::selected) {
    205      SetStates(ElementState::DEFAULT, !!aValue, aNotify);
    206    }
    207  }
    208 
    209  return nsGenericHTMLElement::AfterSetAttr(
    210      aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
    211 }
    212 
    213 void HTMLOptionElement::GetText(nsAString& aText) {
    214  nsAutoString text;
    215 
    216  nsIContent* child = nsINode::GetFirstChild();
    217  while (child) {
    218    if (Text* textChild = child->GetAsText()) {
    219      textChild->AppendTextTo(text);
    220    }
    221    if (child->IsHTMLElement(nsGkAtoms::script) ||
    222        child->IsSVGElement(nsGkAtoms::script)) {
    223      child = child->GetNextNonChildNode(this);
    224    } else {
    225      child = child->GetNextNode(this);
    226    }
    227  }
    228 
    229  // XXX No CompressWhitespace for nsAString.  Sad.
    230  text.CompressWhitespace(true, true);
    231  aText = text;
    232 }
    233 
    234 void HTMLOptionElement::SetText(const nsAString& aText, ErrorResult& aRv) {
    235  aRv = nsContentUtils::SetNodeTextContent(this, aText, false);
    236 }
    237 
    238 nsresult HTMLOptionElement::BindToTree(BindContext& aContext,
    239                                       nsINode& aParent) {
    240  nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
    241  NS_ENSURE_SUCCESS(rv, rv);
    242 
    243  // Our new parent might change :disabled/:enabled state.
    244  UpdateDisabledState(false);
    245 
    246  return NS_OK;
    247 }
    248 
    249 void HTMLOptionElement::UnbindFromTree(UnbindContext& aContext) {
    250  nsGenericHTMLElement::UnbindFromTree(aContext);
    251 
    252  // Our previous parent could have been involved in :disabled/:enabled state.
    253  UpdateDisabledState(false);
    254 }
    255 
    256 // Get the select content element that contains this option
    257 HTMLSelectElement* HTMLOptionElement::GetSelect() const {
    258  nsIContent* parent = GetParent();
    259  if (!parent) {
    260    return nullptr;
    261  }
    262 
    263  HTMLSelectElement* select = HTMLSelectElement::FromNode(parent);
    264  if (select) {
    265    return select;
    266  }
    267 
    268  if (!parent->IsHTMLElement(nsGkAtoms::optgroup)) {
    269    return nullptr;
    270  }
    271 
    272  return HTMLSelectElement::FromNodeOrNull(parent->GetParent());
    273 }
    274 
    275 already_AddRefed<HTMLOptionElement> HTMLOptionElement::Option(
    276    const GlobalObject& aGlobal, const nsAString& aText,
    277    const Optional<nsAString>& aValue, bool aDefaultSelected, bool aSelected,
    278    ErrorResult& aError) {
    279  nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
    280  Document* doc;
    281  if (!win || !(doc = win->GetExtantDoc())) {
    282    aError.Throw(NS_ERROR_FAILURE);
    283    return nullptr;
    284  }
    285 
    286  RefPtr<mozilla::dom::NodeInfo> nodeInfo = doc->NodeInfoManager()->GetNodeInfo(
    287      nsGkAtoms::option, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
    288 
    289  auto* nim = nodeInfo->NodeInfoManager();
    290  RefPtr<HTMLOptionElement> option =
    291      new (nim) HTMLOptionElement(nodeInfo.forget());
    292 
    293  if (!aText.IsEmpty()) {
    294    // Create a new text node and append it to the option
    295    RefPtr<nsTextNode> textContent = new (option->NodeInfo()->NodeInfoManager())
    296        nsTextNode(option->NodeInfo()->NodeInfoManager());
    297 
    298    textContent->SetText(aText, false);
    299 
    300    option->AppendChildTo(textContent, false, aError);
    301    if (aError.Failed()) {
    302      return nullptr;
    303    }
    304  }
    305 
    306  if (aValue.WasPassed()) {
    307    // Set the value attribute for this element. We're calling SetAttr
    308    // directly because we want to pass aNotify == false.
    309    aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
    310                             aValue.Value(), false);
    311    if (aError.Failed()) {
    312      return nullptr;
    313    }
    314  }
    315 
    316  if (aDefaultSelected) {
    317    // We're calling SetAttr directly because we want to pass
    318    // aNotify == false.
    319    aError =
    320        option->SetAttr(kNameSpaceID_None, nsGkAtoms::selected, u""_ns, false);
    321    if (aError.Failed()) {
    322      return nullptr;
    323    }
    324  }
    325 
    326  option->SetSelected(aSelected);
    327  option->SetSelectedChanged(false);
    328 
    329  return option.forget();
    330 }
    331 
    332 nsresult HTMLOptionElement::CopyInnerTo(Element* aDest) {
    333  nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
    334  NS_ENSURE_SUCCESS(rv, rv);
    335 
    336  if (aDest->OwnerDoc()->IsStaticDocument()) {
    337    static_cast<HTMLOptionElement*>(aDest)->SetSelected(Selected());
    338  }
    339  return NS_OK;
    340 }
    341 
    342 JSObject* HTMLOptionElement::WrapNode(JSContext* aCx,
    343                                      JS::Handle<JSObject*> aGivenProto) {
    344  return HTMLOptionElement_Binding::Wrap(aCx, this, aGivenProto);
    345 }
    346 
    347 }  // namespace mozilla::dom