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