diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs
index d3a6d2d5dc9..b5edc6bd7c9 100644
--- a/components/script/dom/htmlelement.rs
+++ b/components/script/dom/htmlelement.rs
@@ -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
}
diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs
index 00377fd8208..4a186530ed2 100644
--- a/components/script/dom/htmlformelement.rs
+++ b/components/script/dom/htmlformelement.rs
@@ -1698,8 +1698,18 @@ pub(crate) trait FormControl: DomObject {
}
}
+ ///
+ fn is_candidate_for_constraint_validation(&self) -> bool {
+ let element = self.to_element();
+ let html_element = element.downcast::();
+ 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;
}
diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
index 7495f6eab78..090190b01f7 100644
--- a/components/script/dom/htmlinputelement.rs
+++ b/components/script/dom/htmlinputelement.rs
@@ -2193,6 +2193,26 @@ impl HTMLInputElement {
self.upcast::().dirty(NodeDamage::Other);
}
+ ///
+ /// 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::().dirty(NodeDamage::Other);
+ }
+
fn update_placeholder_shown_state(&self) {
if !self.input_type().is_textual_or_password() {
return;
diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs
index d3856fa04f3..ab129f9418f 100644
--- a/components/script/dom/htmltextareaelement.rs
+++ b/components/script/dom/htmltextareaelement.rs
@@ -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::().disabled_state() || self.ReadOnly())
@@ -450,6 +450,13 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement {
}
impl HTMLTextAreaElement {
+ ///
+ /// 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();
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 30a4b0e0cb0..521a28721a6 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -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,
diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs
index 2471d3db471..46fb6231bb9 100644
--- a/components/script/webdriver_handlers.rs
+++ b/components/script/webdriver_handlers.rs
@@ -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();
}
+///
+fn element_is_mutable_form_control(element: &Element) -> bool {
+ if let Some(input_element) = element.downcast::() {
+ 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::() {
+ textarea_element.is_mutable()
+ } else {
+ false
+ }
+}
+
+///
+fn clear_a_resettable_element(element: &Element, can_gc: CanGc) -> Result<(), ErrorStatus> {
+ let html_element = element
+ .downcast::()
+ .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::() {
+ if input_element.Value().is_empty() {
+ return Ok(());
+ }
+ } else if let Some(textarea_element) = element.downcast::() {
+ 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::() {
+ input_element.clear(can_gc);
+ } else if let Some(textarea_element) = element.downcast::() {
+ textarea_element.clear();
+ } else {
+ unreachable!("We have confirm previously that element is mutable form control");
+ }
+
+ let event_target = element.upcast::();
+ 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(())
+}
+
+///
+pub(crate) fn handle_element_clear(
+ documents: &DocumentCollection,
+ pipeline: PipelineId,
+ element_id: String,
+ reply: IpcSender>,
+ 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> {
// Get parent for `