webdriver: Implement element clear (#38208)

Initial Implementation of [Element
Clear](https://w3c.github.io/webdriver/#element-clear).

Testing: `tests/wpt/tests/webdriver/tests/classic/element_clear/`

---------

Signed-off-by: PotatoCP <Kenzie.Raditya.Tirtarahardja@huawei.com>
Signed-off-by: Kenzie Raditya Tirtarahardja <kenzieradityatirtarahardja18@gmail.com>
Co-authored-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Kenzie Raditya Tirtarahardja 2025-07-25 01:49:31 +08:00 committed by GitHub
parent 1fb782bc38
commit 4b12ae73fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 175 additions and 367 deletions

View file

@ -1259,7 +1259,6 @@ impl FormControl for HTMLElement {
}
fn to_element(&self) -> &Element {
debug_assert!(self.is_form_associated_custom_element());
self.as_element()
}
@ -1268,5 +1267,5 @@ impl FormControl for HTMLElement {
true
}
// TODO candidate_for_validation, satisfies_constraints traits
// TODO satisfies_constraints traits
}

View file

@ -1698,8 +1698,18 @@ pub(crate) trait FormControl: DomObject {
}
}
/// <https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation>
fn is_candidate_for_constraint_validation(&self) -> bool {
let element = self.to_element();
let html_element = element.downcast::<HTMLElement>();
if let Some(html_element) = html_element {
html_element.is_submittable_element() || element.is_instance_validatable()
} else {
false
}
}
// XXXKiChjang: Implement these on inheritors
// fn candidate_for_validation(&self) -> bool;
// fn satisfies_constraints(&self) -> bool;
}

View file

@ -2193,6 +2193,26 @@ impl HTMLInputElement {
self.upcast::<Node>().dirty(NodeDamage::Other);
}
/// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-3>
/// Used by WebDriver to clear the input element.
pub(crate) fn clear(&self, can_gc: CanGc) {
// Step 1. Reset dirty value and dirty checkedness flags.
self.value_dirty.set(false);
self.checked_changed.set(false);
// Step 2. Set value to empty string.
self.textinput.borrow_mut().set_content(DOMString::from(""));
// Step 3. Set checkedness based on presence of content attribute.
self.update_checked_state(self.DefaultChecked(), false);
self.value_changed(can_gc);
// Step 4. Empty selected files
self.filelist.set(None);
// Step 5. invoke the value sanitization algorithm iff
// the type attribute's current state defines one.
// This is covered in `fn sanitize_value` called below.
self.enable_sanitization();
self.upcast::<Node>().dirty(NodeDamage::Other);
}
fn update_placeholder_shown_state(&self) {
if !self.input_type().is_textual_or_password() {
return;

View file

@ -204,7 +204,7 @@ impl HTMLTextAreaElement {
}
// https://html.spec.whatwg.org/multipage/#concept-fe-mutable
fn is_mutable(&self) -> bool {
pub(crate) fn is_mutable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable
// https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
!(self.upcast::<Element>().disabled_state() || self.ReadOnly())
@ -450,6 +450,13 @@ impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
}
impl HTMLTextAreaElement {
/// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-4>
/// Used by WebDriver to clear the textarea element.
pub(crate) fn clear(&self) {
self.value_dirty.set(false);
self.textinput.borrow_mut().set_content(DOMString::from(""));
}
pub(crate) fn reset(&self) {
// https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control
let mut textinput = self.textinput.borrow_mut();

View file

@ -2250,6 +2250,15 @@ impl ScriptThread {
WebDriverScriptCommand::DeleteCookie(name, reply) => {
webdriver_handlers::handle_delete_cookie(&documents, pipeline_id, name, reply)
},
WebDriverScriptCommand::ElementClear(element_id, reply) => {
webdriver_handlers::handle_element_clear(
&documents,
pipeline_id,
element_id,
reply,
can_gc,
)
},
WebDriverScriptCommand::FindElementsCSSSelector(selector, reply) => {
webdriver_handlers::handle_find_elements_css_selector(
&documents,

View file

@ -41,6 +41,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMeth
use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
@ -64,11 +65,13 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlbodyelement::HTMLBodyElement;
use crate::dom::htmldatalistelement::HTMLDataListElement;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlformelement::FormControl;
use crate::dom::htmliframeelement::HTMLIFrameElement;
use crate::dom::htmlinputelement::{HTMLInputElement, InputType};
use crate::dom::htmloptgroupelement::HTMLOptGroupElement;
use crate::dom::htmloptionelement::HTMLOptionElement;
use crate::dom::htmlselectelement::HTMLSelectElement;
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
use crate::dom::nodelist::NodeList;
use crate::dom::types::ShadowRoot;
@ -1651,6 +1654,107 @@ pub(crate) fn handle_get_url(
.unwrap();
}
/// <https://w3c.github.io/webdriver/#dfn-mutable-form-control-element>
fn element_is_mutable_form_control(element: &Element) -> bool {
if let Some(input_element) = element.downcast::<HTMLInputElement>() {
input_element.is_mutable() &&
matches!(
input_element.input_type(),
InputType::Text |
InputType::Search |
InputType::Url |
InputType::Tel |
InputType::Email |
InputType::Password |
InputType::Date |
InputType::Month |
InputType::Week |
InputType::Time |
InputType::DatetimeLocal |
InputType::Number |
InputType::Range |
InputType::Color |
InputType::File
)
} else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
textarea_element.is_mutable()
} else {
false
}
}
/// <https://w3c.github.io/webdriver/#dfn-clear-a-resettable-element>
fn clear_a_resettable_element(element: &Element, can_gc: CanGc) -> Result<(), ErrorStatus> {
let html_element = element
.downcast::<HTMLElement>()
.ok_or(ErrorStatus::UnknownError)?;
// Step 1 - 2. if element is a candidate for constraint
// validation and value is empty, abort steps.
if html_element.is_candidate_for_constraint_validation() {
if let Some(input_element) = element.downcast::<HTMLInputElement>() {
if input_element.Value().is_empty() {
return Ok(());
}
} else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
if textarea_element.Value().is_empty() {
return Ok(());
}
}
}
// Step 3. Invoke the focusing steps for the element.
html_element.Focus(can_gc);
// Step 4. Run clear algorithm for element.
if let Some(input_element) = element.downcast::<HTMLInputElement>() {
input_element.clear(can_gc);
} else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
textarea_element.clear();
} else {
unreachable!("We have confirm previously that element is mutable form control");
}
let event_target = element.upcast::<EventTarget>();
event_target.fire_bubbling_event(atom!("input"), can_gc);
event_target.fire_bubbling_event(atom!("change"), can_gc);
// Step 5. Run the unfocusing steps for the element.
html_element.Blur(can_gc);
Ok(())
}
/// <https://w3c.github.io/webdriver/#element-clear>
pub(crate) fn handle_element_clear(
documents: &DocumentCollection,
pipeline: PipelineId,
element_id: String,
reply: IpcSender<Result<(), ErrorStatus>>,
can_gc: CanGc,
) {
reply
.send(
get_known_element(documents, pipeline, element_id).and_then(|element| {
// Step 4. If element is not editable, return ErrorStatus::InvalidElementState.
// TODO: editing hosts and content editable elements are not implemented yet,
// hence we currently skip the check
if !element_is_mutable_form_control(&element) {
return Err(ErrorStatus::InvalidElementState);
}
// TODO: Step 5. Scroll Into View
// TODO: Step 6 - 10
// Wait until element become interactable and check.
// Step 11
// TODO: Clear content editable elements
clear_a_resettable_element(&element, can_gc)
}),
)
.unwrap();
}
fn get_option_parent(node: &Node) -> Option<DomRoot<Node>> {
// Get parent for `<option>` or `<optiongrp>` based on container spec:
// > 1. Let datalist parent be the first datalist element reached by traversing the tree