libservo: Allow embedders to signal when the cursor has left the WebView (#37317)

Currently, the hover state will stay when the mouse moves out of the
webview, this PR fixes it

Testing: Hover on the `About` on servo.org, and then move the mouse up
to the browser UI, see the hover state resets

Signed-off-by: Tony <legendmastertony@gmail.com>
This commit is contained in:
Tony 2025-06-18 19:59:11 +08:00 committed by GitHub
parent 0896341285
commit b9fcc95992
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 125 additions and 27 deletions

View file

@ -360,21 +360,33 @@ impl ServoRenderer {
.send_transaction(self.webrender_document, transaction);
}
pub(crate) fn update_cursor(&mut self, pos: DevicePoint, result: &CompositorHitTestResult) {
self.cursor_pos = pos;
let cursor = match result.cursor {
Some(cursor) if cursor != self.cursor => cursor,
_ => return,
};
let Some(webview_id) = self
pub(crate) fn update_cursor_from_hittest(
&mut self,
pos: DevicePoint,
result: &CompositorHitTestResult,
) {
if let Some(webview_id) = self
.pipeline_to_webview_map
.get(&result.pipeline_id)
.cloned()
else {
.copied()
{
self.update_cursor(pos, webview_id, result.cursor);
} else {
warn!("Couldn't update cursor for non-WebView-associated pipeline");
return;
};
}
pub(crate) fn update_cursor(
&mut self,
pos: DevicePoint,
webview_id: WebViewId,
cursor: Option<Cursor>,
) {
self.cursor_pos = pos;
let cursor = match cursor {
Some(cursor) if cursor != self.cursor => cursor,
_ => return,
};
self.cursor = cursor;
@ -631,7 +643,9 @@ impl IOCompositor {
.borrow()
.hit_test_at_point(point, details_for_pipeline);
if let Ok(result) = result {
self.global.borrow_mut().update_cursor(point, &result);
self.global
.borrow_mut()
.update_cursor_from_hittest(point, &result);
}
}

View file

@ -385,8 +385,13 @@ impl WebViewRenderer {
InputEvent::Touch(ref mut touch_event) => {
touch_event.init_sequence_id(self.touch_handler.current_sequence_id);
},
InputEvent::MouseButton(_) | InputEvent::MouseMove(_) | InputEvent::Wheel(_) => {
self.global.borrow_mut().update_cursor(point, &result);
InputEvent::MouseButton(_) |
InputEvent::MouseLeave(_) |
InputEvent::MouseMove(_) |
InputEvent::Wheel(_) => {
self.global
.borrow_mut()
.update_cursor_from_hittest(point, &result);
},
_ => unreachable!("Unexpected input event type: {event:?}"),
}
@ -428,7 +433,9 @@ impl WebViewRenderer {
touch_event.init_sequence_id(self.touch_handler.current_sequence_id);
},
InputEvent::MouseButton(_) | InputEvent::MouseMove(_) | InputEvent::Wheel(_) => {
self.global.borrow_mut().update_cursor(point, &result);
self.global
.borrow_mut()
.update_cursor_from_hittest(point, &result);
},
_ => unreachable!("Unexpected input event type: {event:?}"),
}

View file

@ -97,6 +97,7 @@ mod from_compositor {
InputEvent::Keyboard(..) => target_variant!("Keyboard"),
InputEvent::MouseButton(..) => target_variant!("MouseButton"),
InputEvent::MouseMove(..) => target_variant!("MouseMove"),
InputEvent::MouseLeave(..) => target_variant!("MouseLeave"),
InputEvent::Touch(..) => target_variant!("Touch"),
InputEvent::Wheel(..) => target_variant!("Wheel"),
InputEvent::Scroll(..) => target_variant!("Scroll"),

View file

@ -2113,6 +2113,49 @@ impl Document {
}
}
#[allow(unsafe_code)]
pub(crate) fn handle_mouse_leave_event(
&self,
hit_test_result: Option<CompositorHitTestResult>,
pressed_mouse_buttons: u16,
can_gc: CanGc,
) {
// Ignore all incoming events without a hit test.
let Some(hit_test_result) = hit_test_result else {
return;
};
self.window()
.send_to_embedder(EmbedderMsg::Status(self.webview_id(), None));
let node = unsafe { node::from_untrusted_node_address(hit_test_result.node) };
for element in node
.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
{
element.set_hover_state(false);
element.set_active_state(false);
}
self.fire_mouse_event(
hit_test_result.point_in_viewport,
node.upcast(),
FireMouseEventType::Out,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
pressed_mouse_buttons,
can_gc,
);
self.handle_mouse_enter_leave_event(
hit_test_result.point_in_viewport,
FireMouseEventType::Leave,
None,
node,
pressed_mouse_buttons,
can_gc,
);
}
fn handle_mouse_enter_leave_event(
&self,
client_point: Point2D<f32>,

View file

@ -1127,6 +1127,14 @@ impl ScriptThread {
can_gc,
);
},
InputEvent::MouseLeave(_) => {
self.topmost_mouse_over_target.take();
document.handle_mouse_leave_event(
event.hit_test_result,
event.pressed_mouse_buttons,
can_gc,
);
},
InputEvent::Touch(touch_event) => {
let touch_result =
document.handle_touch_event(touch_event, event.hit_test_result, can_gc);

View file

@ -20,6 +20,7 @@ pub enum InputEvent {
Keyboard(KeyboardEvent),
MouseButton(MouseButtonEvent),
MouseMove(MouseMoveEvent),
MouseLeave(MouseLeaveEvent),
Touch(TouchEvent),
Wheel(WheelEvent),
Scroll(ScrollEvent),
@ -42,6 +43,7 @@ impl InputEvent {
InputEvent::Keyboard(..) => None,
InputEvent::MouseButton(event) => Some(event.point),
InputEvent::MouseMove(event) => Some(event.point),
InputEvent::MouseLeave(event) => Some(event.point),
InputEvent::Touch(event) => Some(event.point),
InputEvent::Wheel(event) => Some(event.point),
InputEvent::Scroll(..) => None,
@ -56,6 +58,7 @@ impl InputEvent {
InputEvent::Keyboard(event) => event.webdriver_id,
InputEvent::MouseButton(event) => event.webdriver_id,
InputEvent::MouseMove(event) => event.webdriver_id,
InputEvent::MouseLeave(..) => None,
InputEvent::Touch(..) => None,
InputEvent::Wheel(event) => event.webdriver_id,
InputEvent::Scroll(..) => None,
@ -76,6 +79,7 @@ impl InputEvent {
InputEvent::MouseMove(ref mut event) => {
event.webdriver_id = webdriver_id;
},
InputEvent::MouseLeave(..) => {},
InputEvent::Touch(..) => {},
InputEvent::Wheel(ref mut event) => {
event.webdriver_id = webdriver_id;
@ -214,6 +218,17 @@ impl MouseMoveEvent {
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct MouseLeaveEvent {
pub point: DevicePoint,
}
impl MouseLeaveEvent {
pub fn new(point: DevicePoint) -> Self {
Self { point }
}
}
/// The type of input represented by a multi-touch event.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum TouchEventType {