diff --git a/components/script/dom/html/htmltextareaelement.rs b/components/script/dom/html/htmltextareaelement.rs index d864eca07d7..493c1b7ffc7 100644 --- a/components/script/dom/html/htmltextareaelement.rs +++ b/components/script/dom/html/htmltextareaelement.rs @@ -25,6 +25,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTex use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::error::ErrorResult; use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType}; @@ -33,6 +34,8 @@ use crate::dom::document::Document; use crate::dom::document_embedder_controls::ControlElement; use crate::dom::element::{AttributeMutation, Element}; use crate::dom::event::Event; +use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed}; +use crate::dom::eventtarget::EventTarget; use crate::dom::html::htmlelement::HTMLElement; use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement; use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement}; @@ -69,6 +72,9 @@ pub(crate) struct HTMLTextAreaElement { #[no_trace] #[conditional_malloc_size_of] shared_selection: SharedSelection, + + /// + has_scheduled_selectionchange_event: Cell, } impl LayoutDom<'_, HTMLTextAreaElement> { @@ -131,6 +137,7 @@ impl HTMLTextAreaElement { validity_state: Default::default(), text_input_widget: Default::default(), shared_selection: Default::default(), + has_scheduled_selectionchange_event: Default::default(), } } @@ -232,6 +239,41 @@ impl HTMLTextAreaElement { self.maybe_update_shared_selection(); } } + + /// + fn schedule_a_selection_change_event(&self) { + // Step 1. If target's has scheduled selectionchange event is true, abort these steps. + if self.has_scheduled_selectionchange_event.get() { + return; + } + // Step 2. Set target's has scheduled selectionchange event to true. + self.has_scheduled_selectionchange_event.set(true); + // Step 3. Queue a task on the user interaction task source to fire a selectionchange event on target. + let this = Trusted::new(self); + self.owner_global() + .task_manager() + .user_interaction_task_source() + .queue( + // https://w3c.github.io/selection-api/#firing-selectionchange-event + task!(selectionchange_task_steps: move |cx| { + let this = this.root(); + // Step 1. Set target's has scheduled selectionchange event to false. + this.has_scheduled_selectionchange_event.set(false); + // Step 2. If target is an element, fire an event named selectionchange, which bubbles and not cancelable, at target. + this.upcast::().fire_event_with_params( + atom!("selectionchange"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + EventComposed::Composed, + CanGc::from_cx(cx), + ); + // Step 3. Otherwise, if target is a document, fire an event named selectionchange, + // which does not bubble and not cancelable, at target. + // + // n/a + }), + ); + } } impl TextControlElement for HTMLTextAreaElement { @@ -263,9 +305,19 @@ impl TextControlElement for HTMLTextAreaElement { let enabled = self.upcast::().focus_state(); let mut shared_selection = self.shared_selection.borrow_mut(); - if range == shared_selection.range && enabled == shared_selection.enabled { + let range_remained_equal = range == shared_selection.range; + if range_remained_equal && enabled == shared_selection.enabled { return; } + + if !range_remained_equal { + // https://w3c.github.io/selection-api/#selectionchange-event + // > When an input or textarea element provide a text selection and its selection changes + // > (in either extent or direction), + // > the user agent must schedule a selectionchange event on the element. + self.schedule_a_selection_change_event(); + } + *shared_selection = ScriptSelection { range, character_range: self diff --git a/components/script/dom/html/input_element/mod.rs b/components/script/dom/html/input_element/mod.rs index 08478ff03ec..98012f6bb66 100644 --- a/components/script/dom/html/input_element/mod.rs +++ b/components/script/dom/html/input_element/mod.rs @@ -44,6 +44,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputE use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; use crate::dom::bindings::error::{Error, ErrorResult}; use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType}; @@ -52,6 +53,7 @@ use crate::dom::document::Document; use crate::dom::document_embedder_controls::ControlElement; use crate::dom::element::{AttributeMutation, Element}; use crate::dom::event::Event; +use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed}; use crate::dom::eventtarget::EventTarget; use crate::dom::filelist::FileList; use crate::dom::globalscope::GlobalScope; @@ -156,6 +158,9 @@ pub(crate) struct HTMLInputElement { validity_state: MutNullableDom, #[no_trace] pending_webdriver_response: RefCell>, + + /// + has_scheduled_selectionchange_event: Cell, } #[derive(JSTraceable)] @@ -212,6 +217,7 @@ impl HTMLInputElement { labels_node_list: MutNullableDom::new(None), validity_state: Default::default(), pending_webdriver_response: Default::default(), + has_scheduled_selectionchange_event: Default::default(), } } @@ -893,6 +899,41 @@ impl HTMLInputElement { fn textinput_mut(&self) -> RefMut<'_, TextInput> { self.textinput.borrow_mut() } + + /// + fn schedule_a_selection_change_event(&self) { + // Step 1. If target's has scheduled selectionchange event is true, abort these steps. + if self.has_scheduled_selectionchange_event.get() { + return; + } + // Step 2. Set target's has scheduled selectionchange event to true. + self.has_scheduled_selectionchange_event.set(true); + // Step 3. Queue a task on the user interaction task source to fire a selectionchange event on target. + let this = Trusted::new(self); + self.owner_global() + .task_manager() + .user_interaction_task_source() + .queue( + // https://w3c.github.io/selection-api/#firing-selectionchange-event + task!(selectionchange_task_steps: move |cx| { + let this = this.root(); + // Step 1. Set target's has scheduled selectionchange event to false. + this.has_scheduled_selectionchange_event.set(false); + // Step 2. If target is an element, fire an event named selectionchange, which bubbles and not cancelable, at target. + this.upcast::().fire_event_with_params( + atom!("selectionchange"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + EventComposed::Composed, + CanGc::from_cx(cx), + ); + // Step 3. Otherwise, if target is a document, fire an event named selectionchange, + // which does not bubble and not cancelable, at target. + // + // n/a + }), + ); + } } impl<'dom> LayoutDom<'dom, HTMLInputElement> { @@ -961,10 +1002,19 @@ impl TextControlElement for HTMLInputElement { let enabled = self.is_textual_or_password() && self.upcast::().focus_state(); let mut shared_selection = self.shared_selection.borrow_mut(); - if range == shared_selection.range && enabled == shared_selection.enabled { + let range_remained_equal = range == shared_selection.range; + if range_remained_equal && enabled == shared_selection.enabled { return; } + if !range_remained_equal { + // https://w3c.github.io/selection-api/#selectionchange-event + // > When an input or textarea element provide a text selection and its selection changes + // > (in either extent or direction), + // > the user agent must schedule a selectionchange event on the element. + self.schedule_a_selection_change_event(); + } + *shared_selection = ScriptSelection { range, character_range: self diff --git a/tests/wpt/meta/selection/fire-selectionchange-event-on-textcontrol-element-on-pressing-backspace.html.ini b/tests/wpt/meta/selection/fire-selectionchange-event-on-textcontrol-element-on-pressing-backspace.html.ini deleted file mode 100644 index eeb5ded8784..00000000000 --- a/tests/wpt/meta/selection/fire-selectionchange-event-on-textcontrol-element-on-pressing-backspace.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[fire-selectionchange-event-on-textcontrol-element-on-pressing-backspace.html] - [selectionchange event fired on an input element] - expected: FAIL diff --git a/tests/wpt/meta/selection/onselectionchange-on-distinct-text-controls.html.ini b/tests/wpt/meta/selection/onselectionchange-on-distinct-text-controls.html.ini deleted file mode 100644 index 86e2ce59c0c..00000000000 --- a/tests/wpt/meta/selection/onselectionchange-on-distinct-text-controls.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[onselectionchange-on-distinct-text-controls.html] - [selectionchange event on each input element fires independently] - expected: FAIL - - [selectionchange event on each textarea element fires independently] - expected: FAIL diff --git a/tests/wpt/meta/selection/textcontrols/selectionchange-bubble.html.ini b/tests/wpt/meta/selection/textcontrols/selectionchange-bubble.html.ini deleted file mode 100644 index 21e1e28f7f5..00000000000 --- a/tests/wpt/meta/selection/textcontrols/selectionchange-bubble.html.ini +++ /dev/null @@ -1,13 +0,0 @@ -[selectionchange-bubble.html] - expected: TIMEOUT - [selectionchange bubbles from input] - expected: TIMEOUT - - [selectionchange bubbles from input when focused] - expected: NOTRUN - - [selectionchange bubbles from textarea] - expected: NOTRUN - - [selectionchange bubbles from textarea when focused] - expected: NOTRUN diff --git a/tests/wpt/meta/selection/textcontrols/selectionchange-on-shadow-dom.html.ini b/tests/wpt/meta/selection/textcontrols/selectionchange-on-shadow-dom.html.ini deleted file mode 100644 index e13ee537f9f..00000000000 --- a/tests/wpt/meta/selection/textcontrols/selectionchange-on-shadow-dom.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[selectionchange-on-shadow-dom.html] - [selectionchange event fired on a shadow dom bubble to the document] - expected: FAIL diff --git a/tests/wpt/meta/selection/textcontrols/selectionchange.html.ini b/tests/wpt/meta/selection/textcontrols/selectionchange.html.ini deleted file mode 100644 index e56b4784364..00000000000 --- a/tests/wpt/meta/selection/textcontrols/selectionchange.html.ini +++ /dev/null @@ -1,132 +0,0 @@ -[selectionchange.html] - [Modifying selectionStart value of the input element] - expected: FAIL - - [Modifying selectionEnd value of the input element] - expected: FAIL - - [Calling setSelectionRange() on the input element] - expected: FAIL - - [Calling select() on the input element] - expected: FAIL - - [Calling setRangeText() on the input element] - expected: FAIL - - [Setting the same selectionStart value twice on the input element] - expected: FAIL - - [Setting the same selectionEnd value twice on the input element] - expected: FAIL - - [Setting the same selection range twice on the input element] - expected: FAIL - - [Calling select() twice on the input element] - expected: FAIL - - [Calling setRangeText() after select() on the input element] - expected: FAIL - - [Calling setRangeText() repeatedly on the input element] - expected: FAIL - - [Modifying selectionStart value of the disconnected input element] - expected: FAIL - - [Modifying selectionEnd value of the disconnected input element] - expected: FAIL - - [Calling setSelectionRange() on the disconnected input element] - expected: FAIL - - [Calling select() on the disconnected input element] - expected: FAIL - - [Calling setRangeText() on the disconnected input element] - expected: FAIL - - [Setting the same selectionStart value twice on the disconnected input element] - expected: FAIL - - [Setting the same selectionEnd value twice on the disconnected input element] - expected: FAIL - - [Setting the same selection range twice on the disconnected input element] - expected: FAIL - - [Calling select() twice on the disconnected input element] - expected: FAIL - - [Calling setRangeText() after select() on the disconnected input element] - expected: FAIL - - [Calling setRangeText() repeatedly on the disconnected input element] - expected: FAIL - - [Modifying selectionStart value of the textarea element] - expected: FAIL - - [Modifying selectionEnd value of the textarea element] - expected: FAIL - - [Calling setSelectionRange() on the textarea element] - expected: FAIL - - [Calling select() on the textarea element] - expected: FAIL - - [Calling setRangeText() on the textarea element] - expected: FAIL - - [Setting the same selectionStart value twice on the textarea element] - expected: FAIL - - [Setting the same selectionEnd value twice on the textarea element] - expected: FAIL - - [Setting the same selection range twice on the textarea element] - expected: FAIL - - [Calling select() twice on the textarea element] - expected: FAIL - - [Calling setRangeText() after select() on the textarea element] - expected: FAIL - - [Calling setRangeText() repeatedly on the textarea element] - expected: FAIL - - [Modifying selectionStart value of the disconnected textarea element] - expected: FAIL - - [Modifying selectionEnd value of the disconnected textarea element] - expected: FAIL - - [Calling setSelectionRange() on the disconnected textarea element] - expected: FAIL - - [Calling select() on the disconnected textarea element] - expected: FAIL - - [Calling setRangeText() on the disconnected textarea element] - expected: FAIL - - [Setting the same selectionStart value twice on the disconnected textarea element] - expected: FAIL - - [Setting the same selectionEnd value twice on the disconnected textarea element] - expected: FAIL - - [Setting the same selection range twice on the disconnected textarea element] - expected: FAIL - - [Calling select() twice on the disconnected textarea element] - expected: FAIL - - [Calling setRangeText() after select() on the disconnected textarea element] - expected: FAIL - - [Calling setRangeText() repeatedly on the disconnected textarea element] - expected: FAIL