script: Do not call "scroll into view" when handling element clicks (#39326)

Previously, when we click any element, it would trigger "scroll into
view". What's worse, for an anchor `<a>`, clicking it would "scroll into
view" instead of navigating to the url until you retry the click. The
reason is that we built `scrollIntoView` into the focus transaction
system with default option. However, the default `preventScroll` for
`FocusOption` is false according to spec, which triggers "scroll into
view" by default with focus triggered by interaction.

This PR
1. Adds spec document for those which really expects "scroll into view",
i.e. `<form>` when validating data.
2. Make sure when we begin focus transaction, we prevent "scroll into
view".
3. `Focus` method of element/document stays unchanged, which by default
scroll into view if no parameter provided according to spec.


Testing: Manually tested on `servo.org` and other websites, and examples
with `<form>` still correctly scroll into view when validation fails.
Fixes: #38616

---------

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
This commit is contained in:
Euclid Ye 2025-09-17 10:23:14 +08:00 committed by GitHub
parent d96b147bab
commit 8c50c44942
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 10 deletions

View file

@ -1161,7 +1161,9 @@ 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(),
focus_options: FocusOptions {
preventScroll: true,
},
});
}
@ -1199,7 +1201,14 @@ impl Document {
focus_initiator: FocusInitiator,
can_gc: CanGc,
) {
self.request_focus_with_options(elem, focus_initiator, FocusOptions::default(), can_gc);
self.request_focus_with_options(
elem,
focus_initiator,
FocusOptions {
preventScroll: true,
},
can_gc,
);
}
/// Request that the given element receive focus once the current

View file

@ -1099,13 +1099,17 @@ impl HTMLFormElement {
/// Interactively validate the constraints of form elements
/// <https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints>
fn interactive_validation(&self, can_gc: CanGc) -> Result<(), ()> {
// Step 1-2
// Step 1 - 2: Statically validate the constraints of form,
// and let `unhandled invalid controls` be the list of elements
// returned if the result was negative.
// If the result was positive, then return that result.
let unhandled_invalid_controls = match self.static_validation(can_gc) {
Ok(()) => return Ok(()),
Err(err) => err,
};
// Step 3
// Step 3: Report the problems with the constraints of at least one of the elements
// given in unhandled invalid controls to the user.
let mut first = true;
for elem in unhandled_invalid_controls {
@ -1114,8 +1118,12 @@ impl HTMLFormElement {
}
if first {
if let Some(html_elem) = elem.downcast::<HTMLElement>() {
// TODO: "Focusing steps" has a different meaning from the focus() method.
// The actual focusing steps should be implemented
// Step 3.1: User agents may focus one of those elements in the process,
// by running the focusing steps for that element,
// and may change the scrolling position of the document, or perform
// some other action that brings the element to the user's attention.
// Here we run focusing steps and scroll element into view.
html_elem.Focus(&FocusOptions::default(), can_gc);
first = false;
}

View file

@ -61,14 +61,15 @@ pub(crate) trait Validatable {
return true;
}
// Step 1.1: Let report be the result of firing an event named invalid at element,
// Step 1.1: Let `report` be the result of firing an event named invalid at element,
// with the cancelable attribute initialized to true.
let report = self
.as_element()
.upcast::<EventTarget>()
.fire_cancelable_event(atom!("invalid"), can_gc);
// Step 1.2.
// Step 1.2. If `report` is true, for the element,
// report the problem, run focusing steps, scroll into view.
if report {
let flags = self.validity_state().invalid_flags();
println!(
@ -76,8 +77,7 @@ pub(crate) trait Validatable {
validation_message_for_flags(&self.validity_state(), flags)
);
if let Some(html_elem) = self.as_element().downcast::<HTMLElement>() {
// TODO: "Focusing steps" has a different meaning from the focus() method.
// The actual focusing steps should be implemented
// Run focusing steps and scroll into view.
html_elem.Focus(&FocusOptions::default(), can_gc);
}
}