script: Add FocusOptions argument to Element.focus and implement FocusOptions.preventScroll (#38495)

This is an implementation of the `prevent_scroll` feature in the focus
transaction system. It allows to control whether focusing an element
should prevent scrolling or not.

Spec:
https://html.spec.whatwg.org/multipage/interaction.html#dom-focusoptions-preventscroll
Testing: Existing WPT tests

Signed-off-by: abdelrahman1234567 <abdelrahman.hossameldin.awadalla@huawei.com>
This commit is contained in:
Abdelrahman Hossam 2025-08-22 22:05:32 +08:00 committed by GitHub
parent 2ac8665e03
commit 176e42d36d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 95 additions and 60 deletions

View file

@ -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<Dom<Element>>,
/// 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,
},
));
));
}
}
}