mirror of
https://github.com/servo/servo.git
synced 2025-09-27 15:20:09 +01:00
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>
114 lines
4.2 KiB
Rust
Executable file
114 lines
4.2 KiB
Rust
Executable file
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
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;
|
|
use crate::dom::element::Element;
|
|
use crate::dom::eventtarget::EventTarget;
|
|
use crate::dom::html::htmldatalistelement::HTMLDataListElement;
|
|
use crate::dom::html::htmlelement::HTMLElement;
|
|
use crate::dom::node::Node;
|
|
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
|
use crate::script_runtime::CanGc;
|
|
|
|
/// Trait for elements with constraint validation support
|
|
pub(crate) trait Validatable {
|
|
fn as_element(&self) -> ∈
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
|
|
fn validity_state(&self) -> DomRoot<ValidityState>;
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation>
|
|
fn is_instance_validatable(&self) -> bool;
|
|
|
|
// Check if element satisfies its constraints, excluding custom errors
|
|
fn perform_validation(
|
|
&self,
|
|
_validate_flags: ValidationFlags,
|
|
_can_gc: CanGc,
|
|
) -> ValidationFlags {
|
|
ValidationFlags::empty()
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#concept-fv-valid>
|
|
fn satisfies_constraints(&self) -> bool {
|
|
self.validity_state().invalid_flags().is_empty()
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#check-validity-steps>
|
|
fn check_validity(&self, can_gc: CanGc) -> bool {
|
|
if self.is_instance_validatable() && !self.satisfies_constraints() {
|
|
self.as_element()
|
|
.upcast::<EventTarget>()
|
|
.fire_cancelable_event(atom!("invalid"), can_gc);
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#report-validity-steps>
|
|
fn report_validity(&self, can_gc: CanGc) -> bool {
|
|
// Step 1.
|
|
if !self.is_instance_validatable() {
|
|
return true;
|
|
}
|
|
|
|
if self.satisfies_constraints() {
|
|
return true;
|
|
}
|
|
|
|
// 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. 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!(
|
|
"Validation error: {}",
|
|
validation_message_for_flags(&self.validity_state(), flags)
|
|
);
|
|
if let Some(html_elem) = self.as_element().downcast::<HTMLElement>() {
|
|
// Run focusing steps and scroll into view.
|
|
html_elem.Focus(&FocusOptions::default(), can_gc);
|
|
}
|
|
}
|
|
|
|
// Step 1.3.
|
|
false
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
|
|
fn validation_message(&self) -> DOMString {
|
|
if self.is_instance_validatable() {
|
|
let flags = self.validity_state().invalid_flags();
|
|
validation_message_for_flags(&self.validity_state(), flags)
|
|
} else {
|
|
DOMString::new()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation>
|
|
pub(crate) fn is_barred_by_datalist_ancestor(elem: &Node) -> bool {
|
|
elem.upcast::<Node>()
|
|
.ancestors()
|
|
.any(|node| node.is::<HTMLDataListElement>())
|
|
}
|
|
|
|
// Get message for given validation flags or custom error message
|
|
fn validation_message_for_flags(state: &ValidityState, failed_flags: ValidationFlags) -> DOMString {
|
|
if failed_flags.contains(ValidationFlags::CUSTOM_ERROR) {
|
|
state.custom_error_message().clone()
|
|
} else {
|
|
DOMString::from(failed_flags.to_string())
|
|
}
|
|
}
|