tor-browser

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

HTMLButtonElement.cpp (22415B)


      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/HTMLButtonElement.h"
      8 
      9 #include "HTMLFormSubmissionConstants.h"
     10 #include "mozAutoDocUpdate.h"
     11 #include "mozilla/ContentEvents.h"
     12 #include "mozilla/EventDispatcher.h"
     13 #include "mozilla/EventStateManager.h"
     14 #include "mozilla/FocusModel.h"
     15 #include "mozilla/MouseEvents.h"
     16 #include "mozilla/PresShell.h"
     17 #include "mozilla/PresState.h"
     18 #include "mozilla/TextEvents.h"
     19 #include "mozilla/dom/CommandEvent.h"
     20 #include "mozilla/dom/Document.h"
     21 #include "mozilla/dom/FormData.h"
     22 #include "mozilla/dom/HTMLButtonElementBinding.h"
     23 #include "mozilla/dom/HTMLFormElement.h"
     24 #include "nsAttrValueInlines.h"
     25 #include "nsAttrValueOrString.h"
     26 #include "nsError.h"
     27 #include "nsFocusManager.h"
     28 #include "nsGkAtoms.h"
     29 #include "nsIContentInlines.h"
     30 #include "nsIFormControl.h"
     31 #include "nsIFrame.h"
     32 #include "nsLayoutUtils.h"
     33 #include "nsPresContext.h"
     34 #include "nsUnicharUtils.h"
     35 
     36 #define NS_IN_SUBMIT_CLICK (1 << 0)
     37 #define NS_OUTER_ACTIVATE_EVENT (1 << 1)
     38 
     39 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Button)
     40 
     41 namespace mozilla::dom {
     42 
     43 static constexpr nsAttrValue::EnumTableEntry kButtonTypeTable[] = {
     44    {"button", FormControlType::ButtonButton},
     45    {"reset", FormControlType::ButtonReset},
     46    {"submit", FormControlType::ButtonSubmit},
     47 };
     48 
     49 static constexpr nsAttrValue::EnumTableEntry kButtonCommandTable[] = {
     50    {"close", Element::Command::Close},
     51    {"hide-popover", Element::Command::HidePopover},
     52 
     53    // Part of "future-invokers" proposal.
     54    // https://open-ui.org/components/future-invokers.explainer/
     55    {"open", Element::Command::Open},
     56 
     57    {"request-close", Element::Command::RequestClose},
     58    {"show-modal", Element::Command::ShowModal},
     59    {"show-popover", Element::Command::ShowPopover},
     60 
     61    // Part of "future-invokers" proposal.
     62    // https://open-ui.org/components/future-invokers.explainer/
     63    {"toggle", Element::Command::Toggle},
     64 
     65    {"toggle-popover", Element::Command::TogglePopover},
     66 };
     67 
     68 // The default type is "button" when the command & commandfor attributes are
     69 // present.
     70 static constexpr const nsAttrValue::EnumTableEntry* kButtonButtonType =
     71    &kButtonTypeTable[0];
     72 
     73 // Default type is 'submit' when the `command` or `commandfor` attributes are
     74 // not present.
     75 static constexpr const nsAttrValue::EnumTableEntry* kButtonSubmitType =
     76    &kButtonTypeTable[2];
     77 
     78 // Construction, destruction
     79 HTMLButtonElement::HTMLButtonElement(
     80    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
     81    FromParser aFromParser)
     82    : nsGenericHTMLFormControlElementWithState(
     83          std::move(aNodeInfo), aFromParser,
     84          FormControlType(kButtonSubmitType->value)),
     85      mDisabledChanged(false),
     86      mInInternalActivate(false),
     87      mInhibitStateRestoration(aFromParser & FROM_PARSER_FRAGMENT) {
     88  // Set up our default state: enabled
     89  AddStatesSilently(ElementState::ENABLED);
     90 }
     91 
     92 HTMLButtonElement::~HTMLButtonElement() = default;
     93 
     94 // nsISupports
     95 
     96 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLButtonElement,
     97                                   nsGenericHTMLFormControlElementWithState,
     98                                   mValidity)
     99 
    100 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(
    101    HTMLButtonElement, nsGenericHTMLFormControlElementWithState,
    102    nsIConstraintValidation)
    103 
    104 void HTMLButtonElement::SetCustomValidity(const nsAString& aError) {
    105  ConstraintValidation::SetCustomValidity(aError);
    106  UpdateValidityElementStates(true);
    107 }
    108 
    109 void HTMLButtonElement::UpdateBarredFromConstraintValidation() {
    110  SetBarredFromConstraintValidation(
    111      mType == FormControlType::ButtonButton ||
    112      mType == FormControlType::ButtonReset ||
    113      HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
    114 }
    115 
    116 void HTMLButtonElement::FieldSetDisabledChanged(bool aNotify) {
    117  // FieldSetDisabledChanged *has* to be called *before*
    118  // UpdateBarredFromConstraintValidation, because the latter depends on our
    119  // disabled state.
    120  nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
    121 
    122  UpdateBarredFromConstraintValidation();
    123  UpdateValidityElementStates(aNotify);
    124 }
    125 
    126 NS_IMPL_ELEMENT_CLONE(HTMLButtonElement)
    127 
    128 void HTMLButtonElement::GetFormEnctype(nsAString& aFormEncType) {
    129  GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag,
    130              aFormEncType);
    131 }
    132 
    133 void HTMLButtonElement::GetFormMethod(nsAString& aFormMethod) {
    134  GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aFormMethod);
    135 }
    136 
    137 bool HTMLButtonElement::InAutoState() const {
    138  const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::type);
    139  return (!attr || attr->Type() != nsAttrValue::eEnum);
    140 }
    141 
    142 // https://html.spec.whatwg.org/multipage/#the-button-element%3Aconcept-submit-button
    143 const nsAttrValue::EnumTableEntry* HTMLButtonElement::ResolveAutoState() const {
    144  // A button element is said to be a submit button if any of the following are
    145  // true: the type attribute is in the Auto state and both the command and
    146  // commandfor content attributes are not present; or
    147  // the type attribute is in the Submit Button state.
    148  if (StaticPrefs::dom_element_commandfor_enabled() &&
    149      (HasAttr(nsGkAtoms::commandfor) || HasAttr(nsGkAtoms::command))) {
    150    return kButtonButtonType;
    151  }
    152  return kButtonSubmitType;
    153 }
    154 
    155 void HTMLButtonElement::GetType(nsAString& aType) {
    156  aType.Truncate();
    157  GetEnumAttr(nsGkAtoms::type, ResolveAutoState()->tag, aType);
    158  MOZ_ASSERT(aType.Length() > 0);
    159 }
    160 
    161 int32_t HTMLButtonElement::TabIndexDefault() { return 0; }
    162 
    163 bool HTMLButtonElement::IsHTMLFocusable(IsFocusableFlags aFlags,
    164                                        bool* aIsFocusable,
    165                                        int32_t* aTabIndex) {
    166  if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
    167          aFlags, aIsFocusable, aTabIndex)) {
    168    return true;
    169  }
    170  *aIsFocusable = IsFormControlDefaultFocusable(aFlags) && !IsDisabled();
    171  return false;
    172 }
    173 
    174 bool HTMLButtonElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
    175                                       const nsAString& aValue,
    176                                       nsIPrincipal* aMaybeScriptedPrincipal,
    177                                       nsAttrValue& aResult) {
    178  if (aNamespaceID == kNameSpaceID_None) {
    179    if (aAttribute == nsGkAtoms::type) {
    180      return aResult.ParseEnumValue(aValue, kButtonTypeTable, false);
    181    }
    182 
    183    if (aAttribute == nsGkAtoms::formmethod) {
    184      return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
    185    }
    186    if (aAttribute == nsGkAtoms::formenctype) {
    187      return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
    188    }
    189 
    190    if (StaticPrefs::dom_element_commandfor_enabled()) {
    191      if (aAttribute == nsGkAtoms::command) {
    192        return aResult.ParseEnumValue(aValue, kButtonCommandTable, false);
    193      }
    194      if (aAttribute == nsGkAtoms::commandfor) {
    195        aResult.ParseAtom(aValue);
    196        return true;
    197      }
    198    }
    199  }
    200 
    201  return nsGenericHTMLFormControlElementWithState::ParseAttribute(
    202      aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
    203 }
    204 
    205 bool HTMLButtonElement::IsDisabledForEvents(WidgetEvent* aEvent) {
    206  return IsElementDisabledForEvents(aEvent, GetPrimaryFrame());
    207 }
    208 
    209 void HTMLButtonElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
    210  aVisitor.mCanHandle = false;
    211 
    212  if (IsDisabledForEvents(aVisitor.mEvent)) {
    213    return;
    214  }
    215 
    216  // Track whether we're in the outermost Dispatch invocation that will
    217  // cause activation of the input.  That is, if we're a click event, or a
    218  // DOMActivate that was dispatched directly, this will be set, but if we're
    219  // a DOMActivate dispatched from click handling, it will not be set.
    220  WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
    221  bool outerActivateEvent =
    222      ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
    223       (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
    224        !mInInternalActivate && aVisitor.mEvent->mOriginalTarget == this));
    225 
    226  if (outerActivateEvent) {
    227    aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
    228    aVisitor.mWantsActivationBehavior = true;
    229  }
    230 
    231  nsGenericHTMLElement::GetEventTargetParent(aVisitor);
    232 }
    233 
    234 void HTMLButtonElement::LegacyPreActivationBehavior(
    235    EventChainVisitor& aVisitor) {
    236  // out-of-spec legacy pre-activation behavior needed because of bug 1803805
    237  if (mType == FormControlType::ButtonSubmit && mForm) {
    238    aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
    239    aVisitor.mItemData = static_cast<Element*>(mForm);
    240    // tell the form that we are about to enter a click handler.
    241    // that means that if there are scripted submissions, the
    242    // latest one will be deferred until after the exit point of the handler.
    243    mForm->OnSubmitClickBegin();
    244  }
    245 }
    246 
    247 nsresult HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
    248  nsresult rv = NS_OK;
    249  if (!aVisitor.mPresContext) {
    250    return rv;
    251  }
    252 
    253  if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
    254    WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
    255    if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
    256        OwnerDoc()->MayHaveDOMActivateListeners()) {
    257      // DOMActive event should be trusted since the activation is actually
    258      // occurred even if the cause is an untrusted click event.
    259      InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
    260      actEvent.mDetail = 1;
    261 
    262      if (RefPtr<PresShell> presShell = aVisitor.mPresContext->GetPresShell()) {
    263        nsEventStatus status = nsEventStatus_eIgnore;
    264        mInInternalActivate = true;
    265        presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
    266        mInInternalActivate = false;
    267 
    268        // If activate is cancelled, we must do the same as when click is
    269        // cancelled (revert the checkbox to its original value).
    270        if (status == nsEventStatus_eConsumeNoDefault) {
    271          aVisitor.mEventStatus = status;
    272        }
    273      }
    274    }
    275  }
    276 
    277  if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
    278    WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
    279    if (keyEvent && keyEvent->IsTrusted()) {
    280      HandleKeyboardActivation(aVisitor);
    281    }
    282 
    283    // Bug 1459231: Temporarily needed till links respect activation target
    284    // Then also remove NS_OUTER_ACTIVATE_EVENT
    285    if ((aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) && mForm &&
    286        (mType == FormControlType::ButtonReset ||
    287         mType == FormControlType::ButtonSubmit)) {
    288      aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
    289    }
    290  }
    291 
    292  return rv;
    293 }
    294 
    295 void EndSubmitClick(EventChainVisitor& aVisitor) {
    296  if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK)) {
    297    nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mItemData));
    298    RefPtr<HTMLFormElement> form = HTMLFormElement::FromNodeOrNull(content);
    299    MOZ_ASSERT(form);
    300    // Tell the form that we are about to exit a click handler,
    301    // so the form knows not to defer subsequent submissions.
    302    // The pending ones that were created during the handler
    303    // will be flushed or forgotten.
    304    form->OnSubmitClickEnd();
    305    // Tell the form to flush a possible pending submission.
    306    // the reason is that the script returned false (the event was
    307    // not ignored) so if there is a stored submission, it needs to
    308    // be submitted immediatelly.
    309    // Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
    310    form->FlushPendingSubmission();
    311  }
    312 }
    313 
    314 // https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:activation-behaviour
    315 void HTMLButtonElement::ActivationBehavior(EventChainPostVisitor& aVisitor) {
    316  if (!aVisitor.mPresContext) {
    317    // Should check whether EndSubmitClick is needed here.
    318    return;
    319  }
    320 
    321  auto endSubmit = MakeScopeExit([&] { EndSubmitClick(aVisitor); });
    322 
    323  // 1. If element is disabled, then return.
    324  if (IsDisabled()) {
    325    return;
    326  }
    327 
    328  // 2. If element's node document is not fully active, then return.
    329 
    330  // 3. If element has a form owner:
    331  if (mForm) {
    332    // Hold a strong ref while dispatching
    333    RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
    334    // 3.1. If element is a submit button, then submit element's form owner from
    335    // element with userInvolvement set to event's user navigation involvement,
    336    // and return.
    337    if (mType == FormControlType::ButtonSubmit) {
    338      form->MaybeSubmit(this);
    339      aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
    340      return;
    341    }
    342    // 3.2. If element's type attribute is in the Reset Button state, then reset
    343    // element's form owner, and return.
    344    if (mType == FormControlType::ButtonReset) {
    345      form->MaybeReset(this);
    346      aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
    347      return;
    348    }
    349    // 3.3. If element's type attribute is in the Auto state, then return.
    350    if (InAutoState()) {
    351      return;
    352    }
    353  }
    354 
    355  // 4. Let target be the result of running element's get the
    356  // commandfor-associated element.
    357  RefPtr<Element> target = GetCommandForElement();
    358 
    359  // 5. If target is not null:
    360  if (target) {
    361    // 5.1. Let command be element's command attribute.
    362    Element::Command command = GetCommand();
    363 
    364    // 5.2. If command is in the Unknown state, then return.
    365    if (command == Command::Invalid) {
    366      return;
    367    }
    368 
    369    // 5.3. Let isPopover be true if target's popover attribute is not in the No
    370    // Popover state; otherwise false.
    371    // 5.4. If isPopover is false and command is not in the Custom state:
    372    // (Checking isPopover is handled as part of IsValidCommandAction)
    373    // 5.4.1. Assert: target's namespace is the HTML namespace.
    374    // 5.4.2. If this standard does not define is valid invoker command steps
    375    // for target's local name, then return.
    376    // 5.4.3. Otherwise, if the result of running target's corresponding is
    377    // valid invoker command steps given command is false, then return.
    378    if (command != Command::Custom && !target->IsValidCommandAction(command)) {
    379      return;
    380    }
    381 
    382    // 5.5. Let continue be the result of firing an event named command at
    383    // target, using CommandEvent, with its command attribute initialized to
    384    // command, its source attribute initialized to element, and its cancelable
    385    // attribute initialized to true.
    386    CommandEventInit init;
    387    GetCommand(init.mCommand);
    388    init.mSource = this;
    389    init.mCancelable = true;
    390    RefPtr<Event> event = CommandEvent::Constructor(this, u"command"_ns, init);
    391    event->SetTrusted(true);
    392    event->SetTarget(target);
    393    EventDispatcher::DispatchDOMEvent(target, nullptr, event, nullptr, nullptr);
    394 
    395    // 5.6. If continue is false, then return.
    396    // 5.7. If target is not connected, then return.
    397    // 5.8. If command is in the Custom state, then return.
    398    if (event->DefaultPrevented() || !target->IsInComposedDoc() ||
    399        command == Command::Custom) {
    400      return;
    401    }
    402 
    403    // Steps 5.9...5.12. handled with HandleCommandInternal:
    404    target->HandleCommandInternal(this, command, IgnoreErrors());
    405 
    406  } else {
    407    nsCOMPtr<Element> eventTarget =
    408        do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
    409    // 6. Otherwise, run the popover target attribute activation behavior given
    410    // element and event's target.
    411    HandlePopoverTargetAction(eventTarget);
    412  }
    413 }
    414 
    415 void HTMLButtonElement::LegacyCanceledActivationBehavior(
    416    EventChainPostVisitor& aVisitor) {
    417  // still need to end submission, see bug 1803805
    418  // e.g. when parent element of button has event handler preventing default
    419  // legacy canceled instead of activation behavior will be run
    420  EndSubmitClick(aVisitor);
    421 }
    422 
    423 nsresult HTMLButtonElement::BindToTree(BindContext& aContext,
    424                                       nsINode& aParent) {
    425  nsresult rv =
    426      nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
    427  NS_ENSURE_SUCCESS(rv, rv);
    428 
    429  UpdateBarredFromConstraintValidation();
    430  UpdateValidityElementStates(false);
    431 
    432  return NS_OK;
    433 }
    434 
    435 void HTMLButtonElement::UnbindFromTree(UnbindContext& aContext) {
    436  nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext);
    437 
    438  UpdateBarredFromConstraintValidation();
    439  UpdateValidityElementStates(false);
    440 }
    441 
    442 NS_IMETHODIMP
    443 HTMLButtonElement::Reset() { return NS_OK; }
    444 
    445 NS_IMETHODIMP
    446 HTMLButtonElement::SubmitNamesValues(FormData* aFormData) {
    447  //
    448  // We only submit if we were the button pressed
    449  //
    450  if (aFormData->GetSubmitterElement() != this) {
    451    return NS_OK;
    452  }
    453 
    454  //
    455  // Get the name (if no name, no submit)
    456  //
    457  nsAutoString name;
    458  GetHTMLAttr(nsGkAtoms::name, name);
    459  if (name.IsEmpty()) {
    460    return NS_OK;
    461  }
    462 
    463  //
    464  // Get the value
    465  //
    466  nsAutoString value;
    467  GetHTMLAttr(nsGkAtoms::value, value);
    468 
    469  //
    470  // Submit
    471  //
    472  return aFormData->AddNameValuePair(name, value);
    473 }
    474 
    475 void HTMLButtonElement::DoneCreatingElement() {
    476  if (!mInhibitStateRestoration) {
    477    GenerateStateKey();
    478    RestoreFormControlState();
    479  }
    480 }
    481 
    482 void HTMLButtonElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
    483                                      const nsAttrValue* aValue, bool aNotify) {
    484  if (aNotify && aName == nsGkAtoms::disabled &&
    485      aNameSpaceID == kNameSpaceID_None) {
    486    mDisabledChanged = true;
    487  }
    488 
    489  return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
    490      aNameSpaceID, aName, aValue, aNotify);
    491 }
    492 
    493 void HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
    494                                     const nsAttrValue* aValue,
    495                                     const nsAttrValue* aOldValue,
    496                                     nsIPrincipal* aSubjectPrincipal,
    497                                     bool aNotify) {
    498  if (aNameSpaceID == kNameSpaceID_None) {
    499    if (aName == nsGkAtoms::type) {
    500      if (aValue && aValue->Type() == nsAttrValue::eEnum) {
    501        mType = FormControlType(aValue->GetEnumValue());
    502      } else {
    503        mType = FormControlType(ResolveAutoState()->value);
    504      }
    505    }
    506 
    507    // If the command/commandfor attributes are added and Type is auto, it may
    508    // need to be recalculated:
    509    if (StaticPrefs::dom_element_commandfor_enabled() &&
    510        (aName == nsGkAtoms::command || aName == nsGkAtoms::commandfor)) {
    511      if (InAutoState()) {
    512        mType = FormControlType(ResolveAutoState()->value);
    513      }
    514    }
    515 
    516    MOZ_ASSERT(mType == FormControlType::ButtonButton ||
    517               mType == FormControlType::ButtonSubmit ||
    518               mType == FormControlType::ButtonReset);
    519 
    520    if (aName == nsGkAtoms::type || aName == nsGkAtoms::disabled ||
    521        aName == nsGkAtoms::command || aName == nsGkAtoms::commandfor) {
    522      if (aName == nsGkAtoms::disabled) {
    523        // This *has* to be called *before* validity state check because
    524        // UpdateBarredFromConstraintValidation depends on our disabled state.
    525        UpdateDisabledState(aNotify);
    526      }
    527 
    528      UpdateBarredFromConstraintValidation();
    529      UpdateValidityElementStates(aNotify);
    530    }
    531  }
    532 
    533  return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
    534      aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
    535 }
    536 
    537 void HTMLButtonElement::SaveState() {
    538  if (!mDisabledChanged) {
    539    return;
    540  }
    541 
    542  PresState* state = GetPrimaryPresState();
    543  if (state) {
    544    // We do not want to save the real disabled state but the disabled
    545    // attribute.
    546    state->disabled() = HasAttr(nsGkAtoms::disabled);
    547    state->disabledSet() = true;
    548  }
    549 }
    550 
    551 bool HTMLButtonElement::RestoreState(PresState* aState) {
    552  if (aState && aState->disabledSet() && !aState->disabled()) {
    553    SetDisabled(false, IgnoreErrors());
    554  }
    555  return false;
    556 }
    557 
    558 void HTMLButtonElement::UpdateValidityElementStates(bool aNotify) {
    559  AutoStateChangeNotifier notifier(*this, aNotify);
    560  RemoveStatesSilently(ElementState::VALIDITY_STATES);
    561  if (!IsCandidateForConstraintValidation()) {
    562    return;
    563  }
    564  if (IsValid()) {
    565    AddStatesSilently(ElementState::VALID | ElementState::USER_VALID);
    566  } else {
    567    AddStatesSilently(ElementState::INVALID | ElementState::USER_INVALID);
    568  }
    569 }
    570 
    571 void HTMLButtonElement::GetCommand(nsAString& aCommand) const {
    572  aCommand.Truncate();
    573  Element::Command command = GetCommand();
    574  if (command == Command::Invalid) {
    575    return;
    576  }
    577  if (command == Command::Custom) {
    578    const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::command);
    579    MOZ_ASSERT(attr->Type() == nsAttrValue::eString ||
    580               attr->Type() == nsAttrValue::eAtom);
    581    aCommand.Assign(nsAttrValueOrString(attr).String());
    582    MOZ_ASSERT(
    583        aCommand.Length() >= 2,
    584        "Custom commands start with '--' so must be atleast 2 chars long!");
    585    MOZ_ASSERT(StringBeginsWith(aCommand, u"--"_ns),
    586               "Custom commands start with '--'");
    587    return;
    588  }
    589  GetEnumAttr(nsGkAtoms::command, "", aCommand);
    590 }
    591 
    592 Element::Command HTMLButtonElement::GetCommand() const {
    593  if (const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::command)) {
    594    if (attr->Type() == nsAttrValue::eEnum) {
    595      auto command = Command(attr->GetEnumValue());
    596      // "open" and "toggle" commands are for the Detials feature, part of
    597      // "future-invokers" proposal. They should not be exposed as valid
    598      // commands unless the details feature is enabled. "close" is also part of
    599      // this feature, but it is also valid for dialogs, so can be exposed.
    600      // https://open-ui.org/components/future-invokers.explainer/
    601      if ((command == Command::Open || command == Command::Toggle) &&
    602          !StaticPrefs::dom_element_commandfor_on_details_enabled()) {
    603        return Command::Invalid;
    604      }
    605      return command;
    606    }
    607    if (StringBeginsWith(nsAttrValueOrString(attr).String(), u"--"_ns)) {
    608      return Command::Custom;
    609    }
    610  }
    611  return Command::Invalid;
    612 }
    613 
    614 Element* HTMLButtonElement::GetCommandForElement() const {
    615  if (StaticPrefs::dom_element_commandfor_enabled()) {
    616    return GetAttrAssociatedElement(nsGkAtoms::commandfor);
    617  }
    618  return nullptr;
    619 }
    620 
    621 void HTMLButtonElement::SetCommandForElement(Element* aElement) {
    622  ExplicitlySetAttrElement(nsGkAtoms::commandfor, aElement);
    623 }
    624 
    625 JSObject* HTMLButtonElement::WrapNode(JSContext* aCx,
    626                                      JS::Handle<JSObject*> aGivenProto) {
    627  return HTMLButtonElement_Binding::Wrap(aCx, this, aGivenProto);
    628 }
    629 
    630 }  // namespace mozilla::dom