LibWeb: Implement the :default pseudo-class
Some checks are pending
CI / Lagom (arm64, Sanitizer_CI, false, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, false, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macos-15, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

This commit is contained in:
Gingeh 2025-05-24 15:24:38 +10:00 committed by Sam Atkins
commit 3fe148f2d4
Notes: github-actions[bot] 2025-05-24 09:32:32 +00:00
9 changed files with 115 additions and 3 deletions

View file

@ -11,6 +11,9 @@
"checked": { "checked": {
"argument": "" "argument": ""
}, },
"default": {
"argument": ""
},
"defined": { "defined": {
"argument": "" "argument": ""
}, },

View file

@ -1037,6 +1037,29 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
return false; return false;
} }
case CSS::PseudoClass::Default: {
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-default
// The :default pseudo-class must match any element falling into one of the following categories:
if (auto const* form_associated_element = as_if<Web::HTML::FormAssociatedElement>(element)) {
// - Submit buttons that are default buttons of their form owner.
if (form_associated_element->is_submit_button() && form_associated_element->form() && form_associated_element->form()->default_button() == form_associated_element)
return true;
// - input elements to which the checked attribute applies and that have a checked attribute
if (auto const* input_element = as_if<Web::HTML::HTMLInputElement>(form_associated_element)) {
if (input_element->checked_applies() && input_element->has_attribute(HTML::AttributeNames::checked))
return true;
}
}
// - option elements that have a selected attribute
else if (auto const* option_element = as_if<Web::HTML::HTMLOptionElement>(element)) {
if (option_element->has_attribute(HTML::AttributeNames::selected))
return true;
}
return false;
}
} }
return false; return false;

View file

@ -75,6 +75,7 @@ static void collect_properties_used_in_has(Selector::SimpleSelector const& selec
case PseudoClass::Link: case PseudoClass::Link:
case PseudoClass::AnyLink: case PseudoClass::AnyLink:
case PseudoClass::LocalLink: case PseudoClass::LocalLink:
case PseudoClass::Default:
if (in_has) if (in_has)
style_invalidation_data.pseudo_classes_used_in_has_selectors.set(pseudo_class.type); style_invalidation_data.pseudo_classes_used_in_has_selectors.set(pseudo_class.type);
break; break;

View file

@ -1176,13 +1176,13 @@ JS::Value HTMLFormElement::named_item_value(FlyString const& name) const
} }
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#default-button // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#default-button
FormAssociatedElement* HTMLFormElement::default_button() FormAssociatedElement* HTMLFormElement::default_button() const
{ {
// A form element's default button is the first submit button in tree order whose form owner is that form element. // A form element's default button is the first submit button in tree order whose form owner is that form element.
FormAssociatedElement* default_button = nullptr; FormAssociatedElement* default_button = nullptr;
root().for_each_in_subtree([&](auto& node) { root().for_each_in_subtree([&](auto& node) {
auto* form_associated_element = dynamic_cast<FormAssociatedElement*>(&node); auto* form_associated_element = const_cast<FormAssociatedElement*>(dynamic_cast<FormAssociatedElement const*>(&node));
if (!form_associated_element) if (!form_associated_element)
return TraversalDecision::Continue; return TraversalDecision::Continue;

View file

@ -102,6 +102,8 @@ public:
String action() const; String action() const;
WebIDL::ExceptionOr<void> set_action(String const&); WebIDL::ExceptionOr<void> set_action(String const&);
FormAssociatedElement* default_button() const;
private: private:
HTMLFormElement(DOM::Document&, DOM::QualifiedName); HTMLFormElement(DOM::Document&, DOM::QualifiedName);
@ -126,7 +128,6 @@ private:
ErrorOr<void> mail_as_body(URL::URL parsed_action, Vector<XHR::FormDataEntry> entry_list, EncodingTypeAttributeState encoding_type, String encoding, GC::Ref<Navigable> target_navigable, Bindings::NavigationHistoryBehavior history_handling, UserNavigationInvolvement user_involvement); ErrorOr<void> mail_as_body(URL::URL parsed_action, Vector<XHR::FormDataEntry> entry_list, EncodingTypeAttributeState encoding_type, String encoding, GC::Ref<Navigable> target_navigable, Bindings::NavigationHistoryBehavior history_handling, UserNavigationInvolvement user_involvement);
void plan_to_navigate_to(URL::URL url, Variant<Empty, String, POSTResource> post_resource, Vector<XHR::FormDataEntry> entry_list, GC::Ref<Navigable> target_navigable, Bindings::NavigationHistoryBehavior history_handling, UserNavigationInvolvement user_involvement); void plan_to_navigate_to(URL::URL url, Variant<Empty, String, POSTResource> post_resource, Vector<XHR::FormDataEntry> entry_list, GC::Ref<Navigable> target_navigable, Bindings::NavigationHistoryBehavior history_handling, UserNavigationInvolvement user_involvement);
FormAssociatedElement* default_button();
size_t number_of_fields_blocking_implicit_submission() const; size_t number_of_fields_blocking_implicit_submission() const;
bool m_firing_submission_events { false }; bool m_firing_submission_events { false };

View file

@ -3027,6 +3027,18 @@ bool HTMLInputElement::required_applies() const
} }
} }
// https://html.spec.whatwg.org/multipage/input.html#do-not-apply
bool HTMLInputElement::checked_applies() const
{
switch (type_state()) {
case TypeAttributeState::Checkbox:
case TypeAttributeState::RadioButton:
return true;
default:
return false;
}
}
bool HTMLInputElement::has_selectable_text() const bool HTMLInputElement::has_selectable_text() const
{ {
// Potential FIXME: Date, Month, Week, Time and LocalDateAndTime are rendered as a basic text input for now, // Potential FIXME: Date, Month, Week, Time and LocalDateAndTime are rendered as a basic text input for now,

View file

@ -220,6 +220,7 @@ public:
bool pattern_applies() const; bool pattern_applies() const;
bool multiple_applies() const; bool multiple_applies() const;
bool required_applies() const; bool required_applies() const;
bool checked_applies() const;
bool has_selectable_text() const; bool has_selectable_text() const;
bool supports_a_picker() const; bool supports_a_picker() const;

View file

@ -0,0 +1,7 @@
Harness status: OK
Found 2 tests
2 Pass
Pass ':default' matches <button>s that are their form's default button, <input>s of type submit/image that are their form's default button, checked <input>s and selected <option>s
Pass ':default' matches dynamically changed form's default buttons

View file

@ -0,0 +1,64 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Selector: pseudo-classes (:default)</title>
<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org" id=link1>
<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes" id=link2>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="utils.js"></script>
<div id="log"></div>
<form>
<button id=button1 type=button>button1</button>
<button id=button2 type=submit>button2</button>
</form>
<form>
<button id=button3 type=reset>button3</button>
<button id=button4>button4</button>
</form>
<button id=button5 type=submit>button5</button>
<form id=form1>
<input type=text id=input1>
</form>
<input type=text id=input2 form=form1>
<form>
<input type=submit id=input3>
<input type=submit id=input4>
</form>
<form>
<input type=image id=input5>
<input type=image id=input6>
</form>
<form>
<input type=submit id=input7>
</form>
<input type=checkbox id=checkbox1 checked>
<input type=checkbox id=checkbox2>
<input type=checkbox id=checkbox3 default>
<input type=radio name=radios id=radio1 checked>
<input type=radio name=radios id=radio2>
<input type=radio name=radios id=radio3 default>
<select id=select1>
<optgroup label="options" id=optgroup1>
<option value="option1" id=option1>option1
<option value="option2" id=option2 selected>option2
</select>
<dialog id="dialog">
<input type=submit id=input8>
</dialog>
<form>
<button id=button6 type='invalid'>button6</button>
<button id=button7>button7</button>
</form>
<form>
<button id=button8>button8</button>
<button id=button9>button9</button>
</form>
<script>
testSelectorIdsMatch(":default", ["button2", "button4", "input3", "input5", "input7", "checkbox1", "radio1", "option2", "button6", "button8"], "':default' matches <button>s that are their form's default button, <input>s of type submit/image that are their form's default button, checked <input>s and selected <option>s");
document.getElementById("button1").type = "submit"; // change the form's default button
testSelectorIdsMatch(":default", ["button1", "button4", "input3", "input5", "input7", "checkbox1", "radio1", "option2", "button6", "button8"], "':default' matches dynamically changed form's default buttons");
</script>