Skip to content

Commit

Permalink
LibWeb/HTML: Update HTMLButtonElement.type to match spec changes
Browse files Browse the repository at this point in the history
Corresponds to part of whatwg/html#9841 and then
whatwg/html#11047

Adding `Auto` as a type state feels a little odd, as it's not an actual
type allowed in HTML. However, it's the default state when the value is
missing or invalid, which works out the same, as long as we never
serialize "auto", which we don't.
  • Loading branch information
AtkinsSJ authored and tcl3 committed Feb 22, 2025
1 parent 03bccb5 commit ff1ef07
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Libraries/LibWeb/HTML/AttributeNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(color, "color") \
__ENUMERATE_HTML_ATTRIBUTE(cols, "cols") \
__ENUMERATE_HTML_ATTRIBUTE(colspan, "colspan") \
__ENUMERATE_HTML_ATTRIBUTE(command, "command") \
__ENUMERATE_HTML_ATTRIBUTE(commandfor, "commandfor") \
__ENUMERATE_HTML_ATTRIBUTE(compact, "compact") \
__ENUMERATE_HTML_ATTRIBUTE(content, "content") \
__ENUMERATE_HTML_ATTRIBUTE(contenteditable, "contenteditable") \
Expand Down
57 changes: 51 additions & 6 deletions Libraries/LibWeb/HTML/HTMLButtonElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,44 @@ HTMLButtonElement::TypeAttributeState HTMLButtonElement::type_state() const
ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTES
#undef __ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE

// The missing value default and invalid value default are the Submit Button state.
return HTMLButtonElement::TypeAttributeState::Submit;
// The attribute's missing value default and invalid value default are both the Auto state.
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-auto-state
return HTMLButtonElement::TypeAttributeState::Auto;
}

WebIDL::ExceptionOr<void> HTMLButtonElement::set_type(String const& type)
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-button-type
String HTMLButtonElement::type_for_bindings() const
{
// The type getter steps are:
// 1. If this is a submit button, then return "submit".
if (is_submit_button())
return "submit"_string;

// 2. Let state be this's type attribute.
auto state = type_state();

// 3. Assert: state is not in the Submit Button state.
VERIFY(state != TypeAttributeState::Submit);

// 4. If state is in the Auto state, then return "button".
if (state == TypeAttributeState::Auto)
return "button"_string;

// 5. Return the keyword value corresponding to state.
switch (state) {
#define __ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(keyword, state) \
case TypeAttributeState::state: \
return #keyword##_string;
ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTES
#undef __ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE
}
VERIFY_NOT_REACHED();
}

// https://html.spec.whatwg.org/multipage/form-elements.html#dom-button-type
WebIDL::ExceptionOr<void> HTMLButtonElement::set_type_for_bindings(String const& type)
{
// The type setter steps are to set the type content attribute to the given value.
return set_attribute(HTML::AttributeNames::type, type);
}

Expand All @@ -68,8 +100,17 @@ i32 HTMLButtonElement::default_tab_index_value() const
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-submit-button
bool HTMLButtonElement::is_submit_button() const
{
// A button element is said to be a submit button if the type attribute is in the Submit Button state.
return type_state() == TypeAttributeState::Submit;
// A button element is said to be a submit button if any of the following are true:
switch (type_state()) {
// - the type attribute is in the Auto state and both the command and commandfor content attributes are not present; or
case TypeAttributeState::Auto:
return !has_attribute(AttributeNames::command) && !has_attribute(AttributeNames::commandfor);
// - the type attribute is in the Submit Button state.
case TypeAttributeState::Submit:
return true;
default:
return false;
}
}

// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-value
Expand Down Expand Up @@ -106,7 +147,11 @@ void HTMLButtonElement::activation_behavior(DOM::Event const& event)
}
}

// 4. Run the popover target attribute activation behavior given element and event's target.
// FIXME: 4. Let target be the result of running element's get the commandfor associated element.
// FIXME: 5. If target is not null:
// ...

// 6. Otherwise, run the popover target attribute activation behavior given element and event's target.
if (event.target() && event.target()->is_dom_node())
PopoverInvokerElement::popover_target_activation_behaviour(*this, as<DOM::Node>(*event.target()));
}
Expand Down
6 changes: 4 additions & 2 deletions Libraries/LibWeb/HTML/HTMLButtonElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ namespace Web::HTML {
#define ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTES \
__ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(submit, Submit) \
__ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(reset, Reset) \
__ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(button, Button)
__ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(button, Button) \
__ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(auto, Auto)

class HTMLButtonElement final
: public HTMLElement
Expand All @@ -38,7 +39,8 @@ class HTMLButtonElement final
};

TypeAttributeState type_state() const;
WebIDL::ExceptionOr<void> set_type(String const&);
String type_for_bindings() const;
WebIDL::ExceptionOr<void> set_type_for_bindings(String const&);

virtual void form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const& namespace_) override;

Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibWeb/HTML/HTMLButtonElement.idl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface HTMLButtonElement : HTMLElement {
[CEReactions, Reflect=formnovalidate] attribute boolean formNoValidate;
[CEReactions, Reflect=formtarget] attribute DOMString formTarget;
[CEReactions, Reflect] attribute DOMString name;
[CEReactions, Reflect, Enumerated=ButtonTypeState] attribute DOMString type;
[CEReactions, ImplementedAs=type_for_bindings, Enumerated=ButtonTypeState] attribute DOMString type;
[CEReactions, Reflect] attribute DOMString value;

[FIXME] readonly attribute boolean willValidate;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Harness status: OK

Found 21 tests

18 Pass
3 Fail
Pass Button with id reset-in-form should reflect type correctly
Pass Button with id submit-in-form should reflect type correctly
Pass Button with id button-in-form should reflect type correctly
Fail Button with id invalid-in-form should reflect type correctly
Pass Button with id missing-in-form should reflect type correctly
Pass Button with id missing-in-form-command-only should reflect type correctly
Pass Button with id missing-in-form-commandfor-only should reflect type correctly
Pass Button with id reset-attr-form should reflect type correctly
Pass Button with id submit-attr-form should reflect type correctly
Pass Button with id button-attr-form should reflect type correctly
Fail Button with id invalid-attr-form should reflect type correctly
Pass Button with id missing-attr-form should reflect type correctly
Pass Button with id missing-attr-form-command-only should reflect type correctly
Pass Button with id missing-attr-form-commandfor-only should reflect type correctly
Pass Button with id reset-outside-form should reflect type correctly
Pass Button with id submit-outside-form should reflect type correctly
Pass Button with id button-outside-form should reflect type correctly
Fail Button with id invalid-outside-form should reflect type correctly
Pass Button with id missing-outside-form should reflect type correctly
Pass Button with id missing-outside-form-command-only should reflect type correctly
Pass Button with id missing-outside-form-commandfor-only should reflect type correctly
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<link rel=author href="mailto:lwarlow@igalia.com">
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>

<iframe name=foo></iframe>
<form id="form" target=foo action="about:blank">
<button id=reset-in-form type=reset commandfor=mypopover command=toggle-popover>reset</button>
<button id=submit-in-form type=submit commandfor=mypopover command=toggle-popover>submit</button>
<button id=button-in-form type=button commandfor=mypopover command=toggle-popover>type=button</button>
<button id=invalid-in-form type=invalid commandfor=mypopover command=toggle-popover>invalid</button>
<button id=missing-in-form commandfor=mypopover command=toggle-popover>missing</button>
<button id=missing-in-form-command-only command=toggle-popover>missing with command only</button>
<button id=missing-in-form-commandfor-only commandfor=mypopover >missing with commandfor only</button>
</form>

<button id=reset-attr-form type=reset commandfor=mypopover command=toggle-popover form=form>reset</button>
<button id=submit-attr-form type=submit commandfor=mypopover command=toggle-popover form=form>submit</button>
<button id=button-attr-form type=button commandfor=mypopover command=toggle-popover form=form>type=button</button>
<button id=invalid-attr-form type=invalid commandfor=mypopover command=toggle-popover form=form>invalid</button>
<button id=missing-attr-form commandfor=mypopover command=toggle-popover form=form>missing</button>
<button id=missing-attr-form-command-only command=toggle-popover form=form>missing with command only</button>
<button id=missing-attr-form-commandfor-only commandfor=mypopover form=form>missing with commandfor only</button>

<button id=reset-outside-form type=reset commandfor=mypopover command=toggle-popover>reset</button>
<button id=submit-outside-form type=submit commandfor=mypopover command=toggle-popover>submit</button>
<button id=button-outside-form type=button commandfor=mypopover command=toggle-popover>type=button</button>
<button id=invalid-outside-form type=invalid commandfor=mypopover command=toggle-popover>invalid</button>
<button id=missing-outside-form commandfor=mypopover command=toggle-popover>missing</button>
<button id=missing-outside-form-command-only command=toggle-popover>missing with command only</button>
<button id=missing-outside-form-commandfor-only commandfor=mypopover>missing with commandfor only</button>

<script>
const data = {
'reset-in-form': 'reset',
'submit-in-form': 'submit',
'button-in-form': 'button',
'invalid-in-form': 'submit',
'missing-in-form': 'button',
'missing-in-form-command-only': 'button',
'missing-in-form-commandfor-only': 'button',
'reset-attr-form': 'reset',
'submit-attr-form': 'submit',
'button-attr-form': 'button',
'invalid-attr-form': 'submit',
'missing-attr-form': 'button',
'missing-attr-form-command-only': 'button',
'missing-attr-form-commandfor-only': 'button',
'reset-outside-form': 'reset',
'submit-outside-form': 'submit',
'button-outside-form': 'button',
'invalid-outside-form': 'submit',
'missing-outside-form': 'button',
'missing-outside-form-command-only': 'button',
'missing-outside-form-commandfor-only': 'button',
};

Object.entries(data).map(([id, expectedType]) => {
test(() => {
const button = document.getElementById(id);
assert_equals(button.type, expectedType, `type of ${id} should be ${expectedType}`);
}, `Button with id ${id} should reflect type correctly`);
});
</script>

0 comments on commit ff1ef07

Please sign in to comment.