script: Pass more information to the MouseEvent constructor (#37672)

- Instead of eagerly computing `pageX` and `pageY`, collect the offset
  from the content's initial containing block in the compositor and pass
  that information through to `MouseEvent`. This prevents a layout flush
  that was happening when eagerly trying to fetch `Document` scroll
  offsets.
- Pass keyboard modifiers properly to `MouseEvent`.
- Now all this information is stored and passed as `Point2D` (typed) and
  `Modifiers` which greatly reduces the amount of arguments that need to
  be passed around.

Testing: It is difficult to test input events as they require WebDriver
which
isn't completely working yet. I have manually run Speedometer 2.1 and I
have
verified that this fixes the regression from #37601.
Fixes: #37601.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-06-25 14:29:27 +02:00 committed by GitHub
parent 50cf01cf3d
commit 0346a62214
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 374 additions and 369 deletions

View file

@ -34,7 +34,8 @@ use embedder_traits::{
UntrustedNodeAddress, WheelEvent,
};
use encoding_rs::{Encoding, UTF_8};
use euclid::default::{Point2D, Rect, Size2D};
use euclid::Point2D;
use euclid::default::{Rect, Size2D};
use fnv::FnvHashMap;
use html5ever::{LocalName, Namespace, QualName, local_name, ns};
use hyper_serde::Serde;
@ -50,7 +51,6 @@ use net_traits::pub_domains::is_pub_domain;
use net_traits::request::{InsecureRequestsPolicy, RequestBuilder};
use net_traits::response::HttpsState;
use net_traits::{FetchResponseListener, IpcSend, ReferrerPolicy};
use num_traits::ToPrimitive;
use percent_encoding::percent_decode;
use profile_traits::ipc as profile_ipc;
use profile_traits::time::TimerMetadataFrameType;
@ -69,6 +69,7 @@ use style::shared_lock::SharedRwLock as StyleSharedRwLock;
use style::str::{split_html_space_chars, str_join};
use style::stylesheet_set::DocumentStylesheetSet;
use style::stylesheets::{Origin, OriginSet, Stylesheet};
use style_traits::CSSPixel;
use stylo_atoms::Atom;
use url::Host;
use uuid::Uuid;
@ -425,7 +426,7 @@ pub(crate) struct Document {
/// <https://w3c.github.io/uievents/#event-type-dblclick>
#[ignore_malloc_size_of = "Defined in std"]
#[no_trace]
last_click_info: DomRefCell<Option<(Instant, Point2D<f32>)>>,
last_click_info: DomRefCell<Option<(Instant, Point2D<f32, CSSPixel>)>>,
/// <https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter>
ignore_destructive_writes_counter: Cell<u32>,
/// <https://html.spec.whatwg.org/multipage/#ignore-opens-during-unload-counter>
@ -1515,12 +1516,11 @@ impl Document {
pub(crate) fn handle_mouse_button_event(
&self,
event: MouseButtonEvent,
hit_test_result: Option<CompositorHitTestResult>,
pressed_mouse_buttons: u16,
input_event: &ConstellationInputEvent,
can_gc: CanGc,
) {
// Ignore all incoming events without a hit test.
let Some(hit_test_result) = hit_test_result else {
let Some(hit_test_result) = &input_event.hit_test_result else {
return;
};
@ -1556,9 +1556,10 @@ impl Document {
let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
event,
pressed_mouse_buttons,
input_event.pressed_mouse_buttons,
&self.window,
&hit_test_result,
hit_test_result,
input_event.active_keyboard_modifiers,
can_gc,
));
@ -1592,23 +1593,13 @@ impl Document {
if self.focus_transaction.borrow().is_some() {
self.commit_focus_transaction(FocusInitiator::Local, can_gc);
}
self.maybe_fire_dblclick(
hit_test_result.point_in_viewport,
node,
pressed_mouse_buttons,
can_gc,
);
self.maybe_fire_dblclick(node, hit_test_result, input_event, can_gc);
}
// When the contextmenu event is triggered by right mouse button
// the contextmenu event MUST be dispatched after the mousedown event.
if let (MouseButtonAction::Down, MouseButton::Right) = (event.action, event.button) {
self.maybe_show_context_menu(
node.upcast(),
pressed_mouse_buttons,
hit_test_result.point_in_viewport,
can_gc,
);
self.maybe_show_context_menu(node.upcast(), hit_test_result, input_event, can_gc);
}
}
@ -1616,13 +1607,10 @@ impl Document {
fn maybe_show_context_menu(
&self,
target: &EventTarget,
pressed_mouse_buttons: u16,
client_point: Point2D<f32>,
hit_test_result: &CompositorHitTestResult,
input_event: &ConstellationInputEvent,
can_gc: CanGc,
) {
let client_x = client_point.x.to_i32().unwrap_or(0);
let client_y = client_point.y.to_i32().unwrap_or(0);
// <https://w3c.github.io/uievents/#contextmenu>
let menu_event = PointerEvent::new(
&self.window, // window
@ -1631,32 +1619,30 @@ impl Document {
EventCancelable::Cancelable, // cancelable
Some(&self.window), // view
0, // detail
client_x, // screen_x
client_y, // screen_y
client_x, // client_x
client_y, // client_y
false, // ctrl_key
false, // alt_key
false, // shift_key
false, // meta_key
2i16, // button, right mouse button
pressed_mouse_buttons, // buttons
None, // related_target
None, // point_in_target
PointerId::Mouse as i32, // pointer_id
1, // width
1, // height
0.5, // pressure
0.0, // tangential_pressure
0, // tilt_x
0, // tilt_y
0, // twist
PI / 2.0, // altitude_angle
0.0, // azimuth_angle
DOMString::from("mouse"), // pointer_type
true, // is_primary
vec![], // coalesced_events
vec![], // predicted_events
hit_test_result.point_in_viewport.to_i32(),
hit_test_result.point_in_viewport.to_i32(),
hit_test_result
.point_relative_to_initial_containing_block
.to_i32(),
input_event.active_keyboard_modifiers,
2i16, // button, right mouse button
input_event.pressed_mouse_buttons,
None, // related_target
None, // point_in_target
PointerId::Mouse as i32, // pointer_id
1, // width
1, // height
0.5, // pressure
0.0, // tangential_pressure
0, // tilt_x
0, // tilt_y
0, // twist
PI / 2.0, // altitude_angle
0.0, // azimuth_angle
DOMString::from("mouse"), // pointer_type
true, // is_primary
vec![], // coalesced_events
vec![], // predicted_events
can_gc,
);
let event = menu_event.upcast::<Event>();
@ -1678,14 +1664,14 @@ impl Document {
fn maybe_fire_dblclick(
&self,
click_pos: Point2D<f32>,
target: &Node,
pressed_mouse_buttons: u16,
hit_test_result: &CompositorHitTestResult,
input_event: &ConstellationInputEvent,
can_gc: CanGc,
) {
// https://w3c.github.io/uievents/#event-type-dblclick
let now = Instant::now();
let point_in_viewport = hit_test_result.point_in_viewport;
let opt = self.last_click_info.borrow_mut().take();
if let Some((last_time, last_pos)) = opt {
@ -1694,7 +1680,7 @@ impl Document {
let DBL_CLICK_DIST_THRESHOLD = pref!(dom_document_dblclick_dist) as u64;
// Calculate distance between this click and the previous click.
let line = click_pos - last_pos;
let line = point_in_viewport - last_pos;
let dist = (line.dot(line) as f64).sqrt();
if now.duration_since(last_time) < DBL_CLICK_TIMEOUT &&
@ -1702,8 +1688,6 @@ impl Document {
{
// A double click has occurred if this click is within a certain time and dist. of previous click.
let click_count = 2;
let client_x = click_pos.x as i32;
let client_y = click_pos.y as i32;
let event = MouseEvent::new(
&self.window,
@ -1712,16 +1696,14 @@ impl Document {
EventCancelable::Cancelable,
Some(&self.window),
click_count,
client_x,
client_y,
client_x,
client_y,
false,
false,
false,
false,
point_in_viewport.to_i32(),
point_in_viewport.to_i32(),
hit_test_result
.point_relative_to_initial_containing_block
.to_i32(),
input_event.active_keyboard_modifiers,
0i16,
pressed_mouse_buttons,
input_event.pressed_mouse_buttons,
None,
None,
can_gc,
@ -1735,23 +1717,20 @@ impl Document {
}
// Update last_click_info with the time and position of the click.
*self.last_click_info.borrow_mut() = Some((now, click_pos));
*self.last_click_info.borrow_mut() = Some((now, point_in_viewport));
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn fire_mouse_event(
&self,
client_point: Point2D<f32>,
target: &EventTarget,
event_name: FireMouseEventType,
can_bubble: EventBubbles,
cancelable: EventCancelable,
pressed_mouse_buttons: u16,
hit_test_result: &CompositorHitTestResult,
input_event: &ConstellationInputEvent,
can_gc: CanGc,
) {
let client_x = client_point.x.to_i32().unwrap_or(0);
let client_y = client_point.y.to_i32().unwrap_or(0);
MouseEvent::new(
&self.window,
DOMString::from(event_name.as_str()),
@ -1759,16 +1738,14 @@ impl Document {
cancelable,
Some(&self.window),
0i32,
client_x,
client_y,
client_x,
client_y,
false,
false,
false,
false,
hit_test_result.point_in_viewport.to_i32(),
hit_test_result.point_in_viewport.to_i32(),
hit_test_result
.point_relative_to_initial_containing_block
.to_i32(),
input_event.active_keyboard_modifiers,
0i16,
pressed_mouse_buttons,
input_event.pressed_mouse_buttons,
None,
None,
can_gc,
@ -1989,13 +1966,12 @@ impl Document {
#[allow(unsafe_code)]
pub(crate) unsafe fn handle_mouse_move_event(
&self,
hit_test_result: Option<CompositorHitTestResult>,
pressed_mouse_buttons: u16,
input_event: &ConstellationInputEvent,
prev_mouse_over_target: &MutNullableDom<Element>,
can_gc: CanGc,
) {
// Ignore all incoming events without a hit test.
let Some(hit_test_result) = hit_test_result else {
let Some(hit_test_result) = &input_event.hit_test_result else {
return;
};
@ -2036,12 +2012,12 @@ impl Document {
}
self.fire_mouse_event(
hit_test_result.point_in_viewport,
old_target.upcast(),
FireMouseEventType::Out,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
hit_test_result,
input_event,
can_gc,
);
@ -2049,11 +2025,11 @@ impl Document {
let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
self.handle_mouse_enter_leave_event(
hit_test_result.point_in_viewport,
FireMouseEventType::Leave,
moving_into,
event_target,
pressed_mouse_buttons,
moving_into,
FireMouseEventType::Leave,
hit_test_result,
input_event,
can_gc,
);
}
@ -2072,12 +2048,12 @@ impl Document {
}
self.fire_mouse_event(
hit_test_result.point_in_viewport,
new_target.upcast(),
FireMouseEventType::Over,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
hit_test_result,
input_event,
can_gc,
);
@ -2086,11 +2062,11 @@ impl Document {
.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
self.handle_mouse_enter_leave_event(
hit_test_result.point_in_viewport,
FireMouseEventType::Enter,
moving_from,
event_target,
pressed_mouse_buttons,
moving_from,
FireMouseEventType::Enter,
hit_test_result,
input_event,
can_gc,
);
}
@ -2098,12 +2074,12 @@ impl Document {
// Send mousemove event to topmost target, unless it's an iframe, in which case the
// compositor should have also sent an event to the inner document.
self.fire_mouse_event(
hit_test_result.point_in_viewport,
new_target.upcast(),
FireMouseEventType::Move,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
hit_test_result,
input_event,
can_gc,
);
@ -2116,12 +2092,11 @@ impl Document {
#[allow(unsafe_code)]
pub(crate) fn handle_mouse_leave_event(
&self,
hit_test_result: Option<CompositorHitTestResult>,
pressed_mouse_buttons: u16,
input_event: &ConstellationInputEvent,
can_gc: CanGc,
) {
// Ignore all incoming events without a hit test.
let Some(hit_test_result) = hit_test_result else {
let Some(hit_test_result) = &input_event.hit_test_result else {
return;
};
@ -2138,31 +2113,31 @@ impl Document {
}
self.fire_mouse_event(
hit_test_result.point_in_viewport,
node.upcast(),
FireMouseEventType::Out,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
hit_test_result,
input_event,
can_gc,
);
self.handle_mouse_enter_leave_event(
hit_test_result.point_in_viewport,
FireMouseEventType::Leave,
None,
node,
pressed_mouse_buttons,
None,
FireMouseEventType::Leave,
hit_test_result,
input_event,
can_gc,
);
}
fn handle_mouse_enter_leave_event(
&self,
client_point: Point2D<f32>,
event_type: FireMouseEventType,
related_target: Option<DomRoot<Node>>,
event_target: DomRoot<Node>,
pressed_mouse_buttons: u16,
related_target: Option<DomRoot<Node>>,
event_type: FireMouseEventType,
hit_test_result: &CompositorHitTestResult,
input_event: &ConstellationInputEvent,
can_gc: CanGc,
) {
assert!(matches!(
@ -2197,12 +2172,12 @@ impl Document {
for target in targets {
self.fire_mouse_event(
client_point,
target.upcast(),
event_type,
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
pressed_mouse_buttons,
hit_test_result,
input_event,
can_gc,
);
}