diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 14c6a4596c7..d7bd5e995e3 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -87,6 +87,7 @@ use crate::dom::bindings::codegen::Bindings::ElementBinding::{ use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods; use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElement_Binding::HTMLIFrameElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions; use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; @@ -264,6 +265,8 @@ struct FocusTransaction { element: Option>, /// See [`Document::has_focus`]. has_focus: bool, + /// Focus options for the transaction + focus_options: FocusOptions, } /// Information about a declarative refresh @@ -1140,6 +1143,7 @@ impl Document { *self.focus_transaction.borrow_mut() = Some(FocusTransaction { element: self.focused.get().as_deref().map(Dom::from_ref), has_focus: self.has_focus.get(), + focus_options: FocusOptions::default(), }); } @@ -1169,15 +1173,27 @@ impl Document { } } + /// Request that the given element receive focus with default options. + /// See [`Self::request_focus_with_options`] for the details. + pub(crate) fn request_focus( + &self, + elem: Option<&Element>, + focus_initiator: FocusInitiator, + can_gc: CanGc, + ) { + self.request_focus_with_options(elem, focus_initiator, FocusOptions::default(), can_gc); + } + /// Request that the given element receive focus once the current /// transaction is complete. `None` specifies to focus the document. /// /// If there's no ongoing transaction, this method automatically starts and /// commits an implicit transaction. - pub(crate) fn request_focus( + pub(crate) fn request_focus_with_options( &self, elem: Option<&Element>, focus_initiator: FocusInitiator, + focus_options: FocusOptions, can_gc: CanGc, ) { // If an element is specified, and it's non-focusable, ignore the @@ -1197,6 +1213,7 @@ impl Document { let focus_transaction = focus_transaction.as_mut().unwrap(); focus_transaction.element = elem.map(Dom::from_ref); focus_transaction.has_focus = true; + focus_transaction.focus_options = focus_options; } if implicit_transaction { @@ -1236,7 +1253,7 @@ impl Document { /// Reassign the focus context to the element that last requested focus during this /// transaction, or the document if no elements requested it. pub(crate) fn commit_focus_transaction(&self, focus_initiator: FocusInitiator, can_gc: CanGc) { - let (mut new_focused, new_focus_state) = { + let (mut new_focused, new_focus_state, prevent_scroll) = { let focus_transaction = self.focus_transaction.borrow(); let focus_transaction = focus_transaction .as_ref() @@ -1247,6 +1264,7 @@ impl Document { .as_ref() .map(|e| DomRoot::from_ref(&**e)), focus_transaction.has_focus, + focus_transaction.focus_options.preventScroll, ) }; *self.focus_transaction.borrow_mut() = None; @@ -1363,16 +1381,19 @@ impl Document { } // Scroll operation to happen after element gets focus. // This is needed to ensure that the focused element is visible. - elem.ScrollIntoView(BooleanOrScrollIntoViewOptions::ScrollIntoViewOptions( - ScrollIntoViewOptions { - parent: ScrollOptions { - behavior: ScrollBehavior::Smooth, + // Only scroll if preventScroll was not specified + if !prevent_scroll { + elem.ScrollIntoView(BooleanOrScrollIntoViewOptions::ScrollIntoViewOptions( + ScrollIntoViewOptions { + parent: ScrollOptions { + behavior: ScrollBehavior::Smooth, + }, + block: ScrollLogicalPosition::Center, + inline: ScrollLogicalPosition::Center, + container: ScrollIntoViewContainer::All, }, - block: ScrollLogicalPosition::Center, - inline: ScrollLogicalPosition::Center, - container: ScrollIntoViewContainer::All, - }, - )); + )); + } } } diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 8f60ce2b286..2af4b278814 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -22,6 +22,7 @@ use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{ }; use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions; use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; @@ -417,12 +418,19 @@ impl HTMLElementMethods for HTMLElement { element.set_click_in_progress(false); } - // https://html.spec.whatwg.org/multipage/#dom-focus - fn Focus(&self, can_gc: CanGc) { + /// + fn Focus(&self, options: &FocusOptions, can_gc: CanGc) { // TODO: Mark the element as locked for focus and run the focusing steps. - // https://html.spec.whatwg.org/multipage/#focusing-steps + // let document = self.owner_document(); - document.request_focus(Some(self.upcast()), FocusInitiator::Local, can_gc); + document.request_focus_with_options( + Some(self.upcast()), + FocusInitiator::Local, + FocusOptions { + preventScroll: options.preventScroll, + }, + can_gc, + ); } // https://html.spec.whatwg.org/multipage/#dom-blur diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 20e6233caec..12c1ddc5767 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -34,6 +34,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMeth use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods; use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions; use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; @@ -1101,7 +1102,9 @@ impl HTMLFormElement { } if first { if let Some(html_elem) = elem.downcast::() { - html_elem.Focus(can_gc); + // TODO: "Focusing steps" has a different meaning from the focus() method. + // The actual focusing steps should be implemented + html_elem.Focus(&FocusOptions::default(), can_gc); first = false; } } diff --git a/components/script/dom/svgelement.rs b/components/script/dom/svgelement.rs index a380dcff5ac..c699e3e9554 100644 --- a/components/script/dom/svgelement.rs +++ b/components/script/dom/svgelement.rs @@ -9,11 +9,12 @@ use script_bindings::str::DOMString; use stylo_dom::ElementState; use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions; use crate::dom::bindings::codegen::Bindings::SVGElementBinding::SVGElementMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; -use crate::dom::document::Document; +use crate::dom::document::{Document, FocusInitiator}; use crate::dom::element::{AttributeMutation, Element}; use crate::dom::node::{Node, NodeTraits}; use crate::dom::virtualmethods::VirtualMethods; @@ -91,7 +92,7 @@ impl VirtualMethods for SVGElement { } impl SVGElementMethods for SVGElement { - // https://html.spec.whatwg.org/multipage/#the-style-attribute + /// fn Style(&self) -> DomRoot { self.style_decl.or_init(|| { let global = self.owner_window(); @@ -105,28 +106,41 @@ impl SVGElementMethods for SVGElement { }) } - // + // https://html.spec.whatwg.org/multipage/#globaleventhandlers global_event_handlers!(); - // https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce + /// fn Nonce(&self) -> DOMString { self.as_element().nonce_value().into() } - // https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce + /// fn SetNonce(&self, value: DOMString) { self.as_element() .update_nonce_internal_slot(value.to_string()) } - // https://html.spec.whatwg.org/multipage/#dom-fe-autofocus + /// fn Autofocus(&self) -> bool { self.element.has_attribute(&local_name!("autofocus")) } - // https://html.spec.whatwg.org/multipage/#dom-fe-autofocus + /// fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) { self.element .set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc); } + + /// + fn Focus(&self, options: &FocusOptions) { + let document = self.element.owner_document(); + document.request_focus_with_options( + Some(&self.element), + FocusInitiator::Local, + FocusOptions { + preventScroll: options.preventScroll, + }, + CanGc::note(), + ); + } } diff --git a/components/script/dom/validation.rs b/components/script/dom/validation.rs index 6a0c88c56ce..0e8f26b58a4 100755 --- a/components/script/dom/validation.rs +++ b/components/script/dom/validation.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods; use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; @@ -75,7 +76,9 @@ pub(crate) trait Validatable { validation_message_for_flags(&self.validity_state(), flags) ); if let Some(html_elem) = self.as_element().downcast::() { - html_elem.Focus(can_gc); + // TODO: "Focusing steps" has a different meaning from the focus() method. + // The actual focusing steps should be implemented + html_elem.Focus(&FocusOptions::default(), can_gc); } } diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 9fa0af15239..b8e85eec675 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -42,6 +42,7 @@ use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions; use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; @@ -1234,7 +1235,9 @@ pub(crate) fn handle_will_send_keys( // run the focusing steps for the element. if let Some(html_element) = element.downcast::() { if !element.is_active_element() { - html_element.Focus(can_gc); + // TODO: "Focusing steps" has a different meaning from the focus() method. + // The actual focusing steps should be implemented + html_element.Focus(&FocusOptions::default(), can_gc); } else { element_has_focus = element.focus_state(); } @@ -1772,7 +1775,9 @@ fn clear_a_resettable_element(element: &Element, can_gc: CanGc) -> Result<(), Er } // Step 3. Invoke the focusing steps for the element. - html_element.Focus(can_gc); + // TODO: "Focusing steps" has a different meaning from the focus() method. + // The actual focusing steps should be implemented + html_element.Focus(&FocusOptions::default(), can_gc); // Step 4. Run clear algorithm for element. if let Some(input_element) = element.downcast::() { @@ -1920,7 +1925,11 @@ pub(crate) fn handle_element_click( // Step 8.5 match container.downcast::() { - Some(html_element) => html_element.Focus(can_gc), + Some(html_element) => { + // TODO: "Focusing steps" has a different meaning from the focus() method. + // The actual focusing steps should be implemented + html_element.Focus(&FocusOptions::default(), can_gc); + }, None => return Err(ErrorStatus::UnknownError), } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 291e4b945a3..33e5f777b9d 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -812,6 +812,10 @@ Dictionaries = { 'derives': ['Clone', 'MallocSizeOf'], }, +'FocusOptions': { + 'derives': ['Clone', 'MallocSizeOf'] +}, + 'FontFaceDescriptors': { 'derives': ['Clone', 'MallocSizeOf'] }, diff --git a/components/script_bindings/webidls/HTMLElement.webidl b/components/script_bindings/webidls/HTMLElement.webidl index 19a4b515d11..19ee9619d44 100644 --- a/components/script_bindings/webidls/HTMLElement.webidl +++ b/components/script_bindings/webidls/HTMLElement.webidl @@ -35,7 +35,6 @@ interface HTMLElement : Element { undefined click(); // [CEReactions] // attribute long tabIndex; - undefined focus(); undefined blur(); // [CEReactions] // attribute DOMString accessKey; diff --git a/components/script_bindings/webidls/HTMLOrSVGElement.webidl b/components/script_bindings/webidls/HTMLOrSVGElement.webidl index 5dd46b2a4ec..f8a5e05fe86 100644 --- a/components/script_bindings/webidls/HTMLOrSVGElement.webidl +++ b/components/script_bindings/webidls/HTMLOrSVGElement.webidl @@ -9,12 +9,17 @@ * liability, trademark and document use rules apply. */ +dictionary FocusOptions { + boolean preventScroll = false; + // boolean focusVisible; +}; + interface mixin HTMLOrSVGElement { // [SameObject] readonly attribute DOMStringMap dataset; attribute DOMString nonce; // intentionally no [CEReactions] [CEReactions] attribute boolean autofocus; // [CEReactions] attribute long tabIndex; - // undefined focus(optional FocusOptions options = {}); + undefined focus(optional FocusOptions options = {}); // undefined blur(); }; diff --git a/tests/wpt/meta/html/dom/idlharness.https.html.ini b/tests/wpt/meta/html/dom/idlharness.https.html.ini index 540fae2ccb6..a9173ff4def 100644 --- a/tests/wpt/meta/html/dom/idlharness.https.html.ini +++ b/tests/wpt/meta/html/dom/idlharness.https.html.ini @@ -5075,9 +5075,6 @@ [SVGElement interface: attribute tabIndex] expected: FAIL - [SVGElement interface: operation focus(optional FocusOptions)] - expected: FAIL - [SVGElement interface: operation blur()] expected: FAIL diff --git a/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html.ini b/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html.ini deleted file mode 100644 index 98f7099d802..00000000000 --- a/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[preventScroll-nested-scroll-elements.html] - [focus(options) - preventScroll on nested scroll elements] - expected: FAIL diff --git a/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll-textarea.html.ini b/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll-textarea.html.ini deleted file mode 100644 index 36ab2472d57..00000000000 --- a/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll-textarea.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[preventScroll-textarea.html] - [preventScroll: true on a textarea element] - expected: FAIL diff --git a/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll.html.ini b/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll.html.ini deleted file mode 100644 index 696745991af..00000000000 --- a/tests/wpt/meta/html/interaction/focus/processing-model/preventScroll.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[preventScroll.html] - [elm.focus({preventScroll: true})] - expected: FAIL diff --git a/tests/wpt/meta/html/interaction/focus/tabindex-focus-flag.html.ini b/tests/wpt/meta/html/interaction/focus/tabindex-focus-flag.html.ini index d23f60c1a9d..88c301d313a 100644 --- a/tests/wpt/meta/html/interaction/focus/tabindex-focus-flag.html.ini +++ b/tests/wpt/meta/html/interaction/focus/tabindex-focus-flag.html.ini @@ -5,15 +5,9 @@ [A with tabindex=invalid should not be focusable.] expected: FAIL - [#svg-a should not be focusable by default.] - expected: FAIL - [input[type="hidden"\] should not be focusable by default.] expected: FAIL - [text with tabindex=0 should be focusable.] - expected: FAIL - [IMG with tabindex=invalid should not be focusable.] expected: FAIL @@ -23,15 +17,6 @@ [#summary-first should be focusable by default.] expected: FAIL - [a with tabindex=0 should be focusable.] - expected: FAIL - - [a with tabindex=-1 should be focusable.] - expected: FAIL - - [#svg-text should not be focusable by default.] - expected: FAIL - [a should not be focusable by default.] expected: FAIL @@ -46,7 +31,3 @@ [a#with-href with tabindex=invalid should not be focusable.] expected: FAIL - - [text with tabindex=-1 should be focusable.] - expected: FAIL -