script: Ensure that leaving the WebView sets the cursor back to the default cursor (#38759)

This changes makes a variety of changes to ensure that the cursor is set
back to the default cursor when it leaves the `WebView`:

1. Display list updates can come after a mouse leaves the `WebView`, so
   when refreshing the cursor after the update, base the updated cursor
   on the last hovered location in the `DocumentEventHandler`, rather
   than the compositor. This allows us to catch when the last hovered
   position is `None` (ie the cursor has left the `WebView`).
2. When handling `MouseLeftViewport` events for the cursor leaving the
   entire WebView, properly set the
   MouseLeftViewport::focus_moving_to_another_iframe` on the input event
   passed to the script thread.
3. When moving out of the `WebView` entirely, explicitly ask the
   embedder to set the cursor back to the default.

Testing: This change adds a unit test verifying this behavior.
Fixes: #38710.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-08-22 00:49:56 -07:00 committed by GitHub
parent 66adf2bf9f
commit 4784ff0375
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 164 additions and 90 deletions

View file

@ -136,8 +136,8 @@ use embedder_traits::{
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
WebDriverCommandResponse, WebDriverLoadStatus, WebDriverScriptCommand,
};
use euclid::Size2D;
use euclid::default::Size2D as UntypedSize2D;
use euclid::{Point2D, Size2D};
use fonts::SystemFontServiceProxy;
use ipc_channel::Error as IpcError;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
@ -164,7 +164,6 @@ use servo_config::prefs::{self, PrefValue};
use servo_config::{opts, pref};
use servo_rand::{Rng, ServoRng, SliceRandom, random};
use servo_url::{Host, ImmutableOrigin, ServoUrl};
use style_traits::CSSPixel;
#[cfg(feature = "webgpu")]
use webgpu::swapchain::WGPUImageMap;
#[cfg(feature = "webgpu")]
@ -1465,8 +1464,8 @@ where
EmbedderToConstellationMessage::ForwardInputEvent(webview_id, event, hit_test) => {
self.forward_input_event(webview_id, event, hit_test);
},
EmbedderToConstellationMessage::RefreshCursor(pipeline_id, point) => {
self.handle_refresh_cursor(pipeline_id, point)
EmbedderToConstellationMessage::RefreshCursor(pipeline_id) => {
self.handle_refresh_cursor(pipeline_id)
},
EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration) => {
for background_monitor_control_sender in &self.background_monitor_control_senders {
@ -3440,14 +3439,14 @@ where
}
#[servo_tracing::instrument(skip_all)]
fn handle_refresh_cursor(&self, pipeline_id: PipelineId, point: Point2D<f32, CSSPixel>) {
fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
return;
};
if let Err(error) = pipeline
.event_loop
.send(ScriptThreadMessage::RefreshCursor(pipeline_id, point))
.send(ScriptThreadMessage::RefreshCursor(pipeline_id))
{
warn!("Could not send RefreshCursor message to pipeline: {error:?}");
}

View file

@ -98,45 +98,46 @@ impl ConstellationWebView {
return;
};
let mut update_hovered_browsing_context = |newly_hovered_browsing_context_id| {
let old_hovered_context_id = std::mem::replace(
&mut self.hovered_browsing_context_id,
newly_hovered_browsing_context_id,
);
if old_hovered_context_id == newly_hovered_browsing_context_id {
return;
}
let Some(old_hovered_context_id) = old_hovered_context_id else {
return;
};
let Some(pipeline) = browsing_contexts
.get(&old_hovered_context_id)
.and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id))
else {
return;
};
let mut update_hovered_browsing_context =
|newly_hovered_browsing_context_id, focus_moving_to_another_iframe: bool| {
let old_hovered_context_id = std::mem::replace(
&mut self.hovered_browsing_context_id,
newly_hovered_browsing_context_id,
);
if old_hovered_context_id == newly_hovered_browsing_context_id {
return;
}
let Some(old_hovered_context_id) = old_hovered_context_id else {
return;
};
let Some(pipeline) = browsing_contexts
.get(&old_hovered_context_id)
.and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id))
else {
return;
};
let mut synthetic_mouse_leave_event = event.clone();
synthetic_mouse_leave_event.event =
InputEvent::MouseLeftViewport(MouseLeftViewportEvent {
focus_moving_to_another_iframe: true,
});
let mut synthetic_mouse_leave_event = event.clone();
synthetic_mouse_leave_event.event =
InputEvent::MouseLeftViewport(MouseLeftViewportEvent {
focus_moving_to_another_iframe,
});
let _ = pipeline
.event_loop
.send(ScriptThreadMessage::SendInputEvent(
pipeline.id,
synthetic_mouse_leave_event,
));
};
let _ = pipeline
.event_loop
.send(ScriptThreadMessage::SendInputEvent(
pipeline.id,
synthetic_mouse_leave_event,
));
};
if let InputEvent::MouseLeftViewport(_) = &event.event {
update_hovered_browsing_context(None);
update_hovered_browsing_context(None, false);
return;
}
if let InputEvent::MouseMove(_) = &event.event {
update_hovered_browsing_context(Some(pipeline.browsing_context_id));
update_hovered_browsing_context(Some(pipeline.browsing_context_id), true);
self.last_mouse_move_point = event
.hit_test_result
.as_ref()