mirror of
https://github.com/servo/servo.git
synced 2025-08-26 23:58:20 +01:00
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:
parent
66adf2bf9f
commit
4784ff0375
11 changed files with 164 additions and 90 deletions
|
@ -1601,7 +1601,6 @@ impl IOCompositor {
|
||||||
.constellation_sender
|
.constellation_sender
|
||||||
.send(EmbedderToConstellationMessage::RefreshCursor(
|
.send(EmbedderToConstellationMessage::RefreshCursor(
|
||||||
hit_test_result.pipeline_id,
|
hit_test_result.pipeline_id,
|
||||||
hit_test_result.point_in_viewport,
|
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
warn!("Sending event to constellation failed ({:?}).", error);
|
warn!("Sending event to constellation failed ({:?}).", error);
|
||||||
|
|
|
@ -136,8 +136,8 @@ use embedder_traits::{
|
||||||
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
|
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
|
||||||
WebDriverCommandResponse, WebDriverLoadStatus, WebDriverScriptCommand,
|
WebDriverCommandResponse, WebDriverLoadStatus, WebDriverScriptCommand,
|
||||||
};
|
};
|
||||||
|
use euclid::Size2D;
|
||||||
use euclid::default::Size2D as UntypedSize2D;
|
use euclid::default::Size2D as UntypedSize2D;
|
||||||
use euclid::{Point2D, Size2D};
|
|
||||||
use fonts::SystemFontServiceProxy;
|
use fonts::SystemFontServiceProxy;
|
||||||
use ipc_channel::Error as IpcError;
|
use ipc_channel::Error as IpcError;
|
||||||
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
||||||
|
@ -164,7 +164,6 @@ use servo_config::prefs::{self, PrefValue};
|
||||||
use servo_config::{opts, pref};
|
use servo_config::{opts, pref};
|
||||||
use servo_rand::{Rng, ServoRng, SliceRandom, random};
|
use servo_rand::{Rng, ServoRng, SliceRandom, random};
|
||||||
use servo_url::{Host, ImmutableOrigin, ServoUrl};
|
use servo_url::{Host, ImmutableOrigin, ServoUrl};
|
||||||
use style_traits::CSSPixel;
|
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
use webgpu::swapchain::WGPUImageMap;
|
use webgpu::swapchain::WGPUImageMap;
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
|
@ -1465,8 +1464,8 @@ where
|
||||||
EmbedderToConstellationMessage::ForwardInputEvent(webview_id, event, hit_test) => {
|
EmbedderToConstellationMessage::ForwardInputEvent(webview_id, event, hit_test) => {
|
||||||
self.forward_input_event(webview_id, event, hit_test);
|
self.forward_input_event(webview_id, event, hit_test);
|
||||||
},
|
},
|
||||||
EmbedderToConstellationMessage::RefreshCursor(pipeline_id, point) => {
|
EmbedderToConstellationMessage::RefreshCursor(pipeline_id) => {
|
||||||
self.handle_refresh_cursor(pipeline_id, point)
|
self.handle_refresh_cursor(pipeline_id)
|
||||||
},
|
},
|
||||||
EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration) => {
|
EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration) => {
|
||||||
for background_monitor_control_sender in &self.background_monitor_control_senders {
|
for background_monitor_control_sender in &self.background_monitor_control_senders {
|
||||||
|
@ -3440,14 +3439,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[servo_tracing::instrument(skip_all)]
|
#[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 {
|
let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(error) = pipeline
|
if let Err(error) = pipeline
|
||||||
.event_loop
|
.event_loop
|
||||||
.send(ScriptThreadMessage::RefreshCursor(pipeline_id, point))
|
.send(ScriptThreadMessage::RefreshCursor(pipeline_id))
|
||||||
{
|
{
|
||||||
warn!("Could not send RefreshCursor message to pipeline: {error:?}");
|
warn!("Could not send RefreshCursor message to pipeline: {error:?}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,45 +98,46 @@ impl ConstellationWebView {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut update_hovered_browsing_context = |newly_hovered_browsing_context_id| {
|
let mut update_hovered_browsing_context =
|
||||||
let old_hovered_context_id = std::mem::replace(
|
|newly_hovered_browsing_context_id, focus_moving_to_another_iframe: bool| {
|
||||||
&mut self.hovered_browsing_context_id,
|
let old_hovered_context_id = std::mem::replace(
|
||||||
newly_hovered_browsing_context_id,
|
&mut self.hovered_browsing_context_id,
|
||||||
);
|
newly_hovered_browsing_context_id,
|
||||||
if old_hovered_context_id == newly_hovered_browsing_context_id {
|
);
|
||||||
return;
|
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(old_hovered_context_id) = old_hovered_context_id else {
|
||||||
};
|
return;
|
||||||
let Some(pipeline) = browsing_contexts
|
};
|
||||||
.get(&old_hovered_context_id)
|
let Some(pipeline) = browsing_contexts
|
||||||
.and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id))
|
.get(&old_hovered_context_id)
|
||||||
else {
|
.and_then(|browsing_context| pipelines.get(&browsing_context.pipeline_id))
|
||||||
return;
|
else {
|
||||||
};
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let mut synthetic_mouse_leave_event = event.clone();
|
let mut synthetic_mouse_leave_event = event.clone();
|
||||||
synthetic_mouse_leave_event.event =
|
synthetic_mouse_leave_event.event =
|
||||||
InputEvent::MouseLeftViewport(MouseLeftViewportEvent {
|
InputEvent::MouseLeftViewport(MouseLeftViewportEvent {
|
||||||
focus_moving_to_another_iframe: true,
|
focus_moving_to_another_iframe,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = pipeline
|
let _ = pipeline
|
||||||
.event_loop
|
.event_loop
|
||||||
.send(ScriptThreadMessage::SendInputEvent(
|
.send(ScriptThreadMessage::SendInputEvent(
|
||||||
pipeline.id,
|
pipeline.id,
|
||||||
synthetic_mouse_leave_event,
|
synthetic_mouse_leave_event,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
if let InputEvent::MouseLeftViewport(_) = &event.event {
|
if let InputEvent::MouseLeftViewport(_) = &event.event {
|
||||||
update_hovered_browsing_context(None);
|
update_hovered_browsing_context(None, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let InputEvent::MouseMove(_) = &event.event {
|
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
|
self.last_mouse_move_point = event
|
||||||
.hit_test_result
|
.hit_test_result
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
@ -79,7 +79,7 @@ pub(crate) struct DocumentEventHandler {
|
||||||
current_hover_target: MutNullableDom<Element>,
|
current_hover_target: MutNullableDom<Element>,
|
||||||
/// The most recent mouse movement point, used for processing `mouseleave` events.
|
/// The most recent mouse movement point, used for processing `mouseleave` events.
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
most_recent_mousemove_point: Point2D<f32, CSSPixel>,
|
most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
|
||||||
/// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered
|
/// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered
|
||||||
/// by the cursor.
|
/// by the cursor.
|
||||||
#[no_trace]
|
#[no_trace]
|
||||||
|
@ -212,13 +212,15 @@ impl DocumentEventHandler {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_cursor(&self, cursor: Cursor) {
|
pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
|
||||||
if Some(cursor) == self.current_cursor.get() {
|
if cursor == self.current_cursor.get() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.current_cursor.set(Some(cursor));
|
self.current_cursor.set(cursor);
|
||||||
self.window
|
self.window.send_to_embedder(EmbedderMsg::SetCursor(
|
||||||
.send_to_embedder(EmbedderMsg::SetCursor(self.window.webview_id(), cursor));
|
self.window.webview_id(),
|
||||||
|
cursor.unwrap_or_default(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mouse_left_viewport_event(
|
fn handle_mouse_left_viewport_event(
|
||||||
|
@ -238,8 +240,9 @@ impl DocumentEventHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hit_test_result) = self
|
if let Some(hit_test_result) = self
|
||||||
.window
|
.most_recent_mousemove_point
|
||||||
.hit_test_from_point_in_viewport(self.most_recent_mousemove_point)
|
.get()
|
||||||
|
.and_then(|point| self.window.hit_test_from_point_in_viewport(point))
|
||||||
{
|
{
|
||||||
MouseEvent::new_simple(
|
MouseEvent::new_simple(
|
||||||
&self.window,
|
&self.window,
|
||||||
|
@ -263,15 +266,23 @@ impl DocumentEventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.current_cursor.set(None);
|
// We do not want to always inform the embedder that cursor has been set to the
|
||||||
self.current_hover_target.set(None);
|
// default cursor, in order to avoid a timing issue when moving between `<iframe>`
|
||||||
|
// elements. There is currently no way to control which `SetCursor` message will
|
||||||
// If focus is moving to another frame, it will decide what the new status text is, but if
|
// reach the embedder first. This is safer when leaving the `WebView` entirely.
|
||||||
// this mouse leave event is leaving the WebView entirely, then clear it.
|
|
||||||
if !mouse_leave_event.focus_moving_to_another_iframe {
|
if !mouse_leave_event.focus_moving_to_another_iframe {
|
||||||
|
// If focus is moving to another frame, it will decide what the new status
|
||||||
|
// text is, but if this mouse leave event is leaving the WebView entirely,
|
||||||
|
// then clear it.
|
||||||
self.window
|
self.window
|
||||||
.send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
|
.send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
|
||||||
|
self.set_cursor(None);
|
||||||
|
} else {
|
||||||
|
self.current_cursor.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.current_hover_target.set(None);
|
||||||
|
self.most_recent_mousemove_point.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mouse_enter_leave_event(
|
fn handle_mouse_enter_leave_event(
|
||||||
|
@ -336,7 +347,7 @@ impl DocumentEventHandler {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the cursor when the mouse moves, if it has changed.
|
// Update the cursor when the mouse moves, if it has changed.
|
||||||
self.set_cursor(hit_test_result.cursor);
|
self.set_cursor(Some(hit_test_result.cursor));
|
||||||
|
|
||||||
let Some(new_target) = hit_test_result
|
let Some(new_target) = hit_test_result
|
||||||
.node
|
.node
|
||||||
|
@ -451,6 +462,8 @@ impl DocumentEventHandler {
|
||||||
.fire(new_target.upcast(), can_gc);
|
.fire(new_target.upcast(), can_gc);
|
||||||
|
|
||||||
self.update_current_hover_target_and_status(Some(new_target));
|
self.update_current_hover_target_and_status(Some(new_target));
|
||||||
|
self.most_recent_mousemove_point
|
||||||
|
.set(Some(hit_test_result.point_in_frame));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
|
fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
|
||||||
|
@ -502,6 +515,21 @@ impl DocumentEventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_refresh_cursor(&self) {
|
||||||
|
let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(hit_test_result) = self
|
||||||
|
.window
|
||||||
|
.hit_test_from_point_in_viewport(most_recent_mousemove_point)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_cursor(Some(hit_test_result.cursor));
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://w3c.github.io/uievents/#mouseevent-algorithms>
|
/// <https://w3c.github.io/uievents/#mouseevent-algorithms>
|
||||||
/// Handles native mouse down, mouse up, mouse click.
|
/// Handles native mouse down, mouse up, mouse click.
|
||||||
fn handle_native_mouse_button_event(
|
fn handle_native_mouse_button_event(
|
||||||
|
|
|
@ -3067,22 +3067,6 @@ impl Window {
|
||||||
node.dirty(NodeDamage::Other);
|
node.dirty(NodeDamage::Other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_refresh_cursor(&self, cursor_position: Point2D<f32, CSSPixel>) {
|
|
||||||
let layout = self.layout.borrow();
|
|
||||||
layout.ensure_stacking_context_tree(self.viewport_details.get());
|
|
||||||
let Some(hit_test_result) = layout
|
|
||||||
.query_elements_from_point(cursor_position.cast_unit(), ElementsFromPointFlags::empty())
|
|
||||||
.into_iter()
|
|
||||||
.nth(0)
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.Document()
|
|
||||||
.event_handler()
|
|
||||||
.set_cursor(hit_test_result.cursor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
|
|
@ -91,7 +91,6 @@ use script_traits::{
|
||||||
use servo_config::{opts, prefs};
|
use servo_config::{opts, prefs};
|
||||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||||
use style::thread_state::{self, ThreadState};
|
use style::thread_state::{self, ThreadState};
|
||||||
use style_traits::CSSPixel;
|
|
||||||
use stylo_atoms::Atom;
|
use stylo_atoms::Atom;
|
||||||
use timers::{TimerEventRequest, TimerId, TimerScheduler};
|
use timers::{TimerEventRequest, TimerId, TimerScheduler};
|
||||||
use url::Position;
|
use url::Position;
|
||||||
|
@ -1884,8 +1883,8 @@ impl ScriptThread {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ScriptThreadMessage::RefreshCursor(pipeline_id, cursor_position) => {
|
ScriptThreadMessage::RefreshCursor(pipeline_id) => {
|
||||||
self.handle_refresh_cursor(pipeline_id, cursor_position);
|
self.handle_refresh_cursor(pipeline_id);
|
||||||
},
|
},
|
||||||
ScriptThreadMessage::PreferencesUpdated(updates) => {
|
ScriptThreadMessage::PreferencesUpdated(updates) => {
|
||||||
let mut current_preferences = prefs::get().clone();
|
let mut current_preferences = prefs::get().clone();
|
||||||
|
@ -3996,15 +3995,11 @@ impl ScriptThread {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_refresh_cursor(
|
fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
|
||||||
&self,
|
let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
|
||||||
pipeline_id: PipelineId,
|
|
||||||
cursor_position: Point2D<f32, CSSPixel>,
|
|
||||||
) {
|
|
||||||
let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
window.handle_refresh_cursor(cursor_position);
|
document.event_handler().handle_refresh_cursor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,8 @@ pub(crate) fn run_test(
|
||||||
|
|
||||||
pub struct ServoTest {
|
pub struct ServoTest {
|
||||||
servo: Servo,
|
servo: Servo,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub rendering_context: Rc<dyn RenderingContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for ServoTest {
|
impl Drop for ServoTest {
|
||||||
|
@ -100,7 +102,10 @@ impl ServoTest {
|
||||||
.event_loop_waker(Box::new(EventLoopWakerImpl(user_event_triggered)));
|
.event_loop_waker(Box::new(EventLoopWakerImpl(user_event_triggered)));
|
||||||
let builder = customize(builder);
|
let builder = customize(builder);
|
||||||
let servo = builder.build();
|
let servo = builder.build();
|
||||||
Self { servo }
|
Self {
|
||||||
|
servo,
|
||||||
|
rendering_context,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn servo(&self) -> &Servo {
|
pub fn servo(&self) -> &Servo {
|
||||||
|
@ -136,11 +141,15 @@ impl ServoTest {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct WebViewDelegateImpl {
|
pub(crate) struct WebViewDelegateImpl {
|
||||||
pub(crate) url_changed: Cell<bool>,
|
pub(crate) url_changed: Cell<bool>,
|
||||||
|
pub(crate) cursor_changed: Cell<bool>,
|
||||||
|
pub(crate) new_frame_ready: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebViewDelegateImpl {
|
impl WebViewDelegateImpl {
|
||||||
pub(crate) fn reset(&self) {
|
pub(crate) fn reset(&self) {
|
||||||
self.url_changed.set(false);
|
self.url_changed.set(false);
|
||||||
|
self.cursor_changed.set(false);
|
||||||
|
self.new_frame_ready.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +157,15 @@ impl WebViewDelegate for WebViewDelegateImpl {
|
||||||
fn notify_url_changed(&self, _webview: servo::WebView, _url: url::Url) {
|
fn notify_url_changed(&self, _webview: servo::WebView, _url: url::Url) {
|
||||||
self.url_changed.set(true);
|
self.url_changed.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn notify_cursor_changed(&self, _webview: WebView, _: servo::Cursor) {
|
||||||
|
self.cursor_changed.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notify_new_frame_ready(&self, webview: WebView) {
|
||||||
|
self.new_frame_ready.set(true);
|
||||||
|
webview.paint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn evaluate_javascript(
|
pub(crate) fn evaluate_javascript(
|
||||||
|
|
|
@ -15,7 +15,11 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use anyhow::ensure;
|
use anyhow::ensure;
|
||||||
use common::{ServoTest, WebViewDelegateImpl, evaluate_javascript, run_api_tests};
|
use common::{ServoTest, WebViewDelegateImpl, evaluate_javascript, run_api_tests};
|
||||||
use servo::{JSValue, JavaScriptEvaluationError, Theme, WebViewBuilder};
|
use euclid::Point2D;
|
||||||
|
use servo::{
|
||||||
|
Cursor, InputEvent, JSValue, JavaScriptEvaluationError, LoadStatus, MouseLeftViewportEvent,
|
||||||
|
MouseMoveEvent, Theme, WebViewBuilder,
|
||||||
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
fn test_create_webview(servo_test: &ServoTest) -> Result<(), anyhow::Error> {
|
fn test_create_webview(servo_test: &ServoTest) -> Result<(), anyhow::Error> {
|
||||||
|
@ -141,9 +145,54 @@ fn test_theme_change(servo_test: &ServoTest) -> Result<(), anyhow::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_cursor_change(servo_test: &ServoTest) -> Result<(), anyhow::Error> {
|
||||||
|
let delegate = Rc::new(WebViewDelegateImpl::default());
|
||||||
|
let webview = WebViewBuilder::new(servo_test.servo())
|
||||||
|
.delegate(delegate.clone())
|
||||||
|
.url(
|
||||||
|
Url::parse(
|
||||||
|
"data:text/html,<!DOCTYPE html><style> html { cursor: crosshair; margin: 0}</style><body>hello</body>",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
webview.focus();
|
||||||
|
webview.show(true);
|
||||||
|
webview.move_resize(servo_test.rendering_context.size2d().to_f32().into());
|
||||||
|
|
||||||
|
let load_webview = webview.clone();
|
||||||
|
let _ = servo_test.spin(move || Ok(load_webview.load_status() != LoadStatus::Complete));
|
||||||
|
|
||||||
|
// Wait for at least one frame after the load completes.
|
||||||
|
delegate.reset();
|
||||||
|
let captured_delegate = delegate.clone();
|
||||||
|
servo_test.spin(move || Ok(!captured_delegate.new_frame_ready.get()))?;
|
||||||
|
|
||||||
|
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(Point2D::new(
|
||||||
|
10., 10.,
|
||||||
|
))));
|
||||||
|
|
||||||
|
let captured_delegate = delegate.clone();
|
||||||
|
servo_test.spin(move || Ok(!captured_delegate.cursor_changed.get()))?;
|
||||||
|
ensure!(webview.cursor() == Cursor::Crosshair);
|
||||||
|
|
||||||
|
delegate.reset();
|
||||||
|
webview.notify_input_event(InputEvent::MouseLeftViewport(
|
||||||
|
MouseLeftViewportEvent::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let captured_delegate = delegate.clone();
|
||||||
|
servo_test.spin(move || Ok(!captured_delegate.cursor_changed.get()))?;
|
||||||
|
ensure!(webview.cursor() == Cursor::Default);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
run_api_tests!(
|
run_api_tests!(
|
||||||
test_create_webview,
|
test_create_webview,
|
||||||
|
test_cursor_change,
|
||||||
test_evaluate_javascript_basic,
|
test_evaluate_javascript_basic,
|
||||||
test_evaluate_javascript_panic,
|
test_evaluate_javascript_panic,
|
||||||
test_theme_change,
|
test_theme_change,
|
||||||
|
|
|
@ -22,7 +22,6 @@ use embedder_traits::{
|
||||||
CompositorHitTestResult, FocusId, InputEvent, JavaScriptEvaluationId, MediaSessionActionType,
|
CompositorHitTestResult, FocusId, InputEvent, JavaScriptEvaluationId, MediaSessionActionType,
|
||||||
Theme, TraversalId, ViewportDetails, WebDriverCommandMsg, WebDriverCommandResponse,
|
Theme, TraversalId, ViewportDetails, WebDriverCommandMsg, WebDriverCommandResponse,
|
||||||
};
|
};
|
||||||
use euclid::Point2D;
|
|
||||||
pub use from_script_message::*;
|
pub use from_script_message::*;
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
|
@ -32,7 +31,6 @@ use servo_config::prefs::PrefValue;
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
pub use structured_data::*;
|
pub use structured_data::*;
|
||||||
use strum_macros::IntoStaticStr;
|
use strum_macros::IntoStaticStr;
|
||||||
use style_traits::CSSPixel;
|
|
||||||
use webrender_api::units::LayoutVector2D;
|
use webrender_api::units::LayoutVector2D;
|
||||||
use webrender_api::{ExternalScrollId, ImageKey};
|
use webrender_api::{ExternalScrollId, ImageKey};
|
||||||
|
|
||||||
|
@ -78,9 +76,10 @@ pub enum EmbedderToConstellationMessage {
|
||||||
BlurWebView,
|
BlurWebView,
|
||||||
/// Forward an input event to an appropriate ScriptTask.
|
/// Forward an input event to an appropriate ScriptTask.
|
||||||
ForwardInputEvent(WebViewId, InputEvent, Option<CompositorHitTestResult>),
|
ForwardInputEvent(WebViewId, InputEvent, Option<CompositorHitTestResult>),
|
||||||
/// Request that the given pipeline do a hit test at the location and reset the
|
/// Request that the given pipeline refresh the cursor by doing a hit test at the most
|
||||||
/// cursor accordingly. This happens after a display list update is rendered.
|
/// recently hovered cursor position and resetting the cursor. This happens after a
|
||||||
RefreshCursor(PipelineId, Point2D<f32, CSSPixel>),
|
/// display list update is rendered.
|
||||||
|
RefreshCursor(PipelineId),
|
||||||
/// Enable the sampling profiler, with a given sampling rate and max total sampling duration.
|
/// Enable the sampling profiler, with a given sampling rate and max total sampling duration.
|
||||||
ToggleProfiler(Duration, Duration),
|
ToggleProfiler(Duration, Duration),
|
||||||
/// Request to exit from fullscreen mode
|
/// Request to exit from fullscreen mode
|
||||||
|
|
|
@ -55,9 +55,10 @@ pub enum ShutdownState {
|
||||||
/// A cursor for the window. This is different from a CSS cursor (see
|
/// A cursor for the window. This is different from a CSS cursor (see
|
||||||
/// `CursorKind`) in that it has no `Auto` value.
|
/// `CursorKind`) in that it has no `Auto` value.
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
|
||||||
pub enum Cursor {
|
pub enum Cursor {
|
||||||
None,
|
None,
|
||||||
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
Pointer,
|
Pointer,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
|
|
|
@ -32,7 +32,7 @@ use embedder_traits::{
|
||||||
CompositorHitTestResult, FocusSequenceNumber, InputEvent, JavaScriptEvaluationId,
|
CompositorHitTestResult, FocusSequenceNumber, InputEvent, JavaScriptEvaluationId,
|
||||||
MediaSessionActionType, Theme, ViewportDetails, WebDriverScriptCommand,
|
MediaSessionActionType, Theme, ViewportDetails, WebDriverScriptCommand,
|
||||||
};
|
};
|
||||||
use euclid::{Point2D, Rect, Scale, Size2D, UnknownUnit};
|
use euclid::{Rect, Scale, Size2D, UnknownUnit};
|
||||||
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
||||||
use keyboard_types::Modifiers;
|
use keyboard_types::Modifiers;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
|
@ -147,9 +147,10 @@ pub enum ScriptThreadMessage {
|
||||||
ExitScriptThread,
|
ExitScriptThread,
|
||||||
/// Sends a DOM event.
|
/// Sends a DOM event.
|
||||||
SendInputEvent(PipelineId, ConstellationInputEvent),
|
SendInputEvent(PipelineId, ConstellationInputEvent),
|
||||||
/// Ask that the given pipeline refreshes the cursor (after a display list render) based
|
/// Request that the given pipeline refresh the cursor by doing a hit test at the most
|
||||||
/// on the hit test at the given point.
|
/// recently hovered cursor position and resetting the cursor. This happens after a
|
||||||
RefreshCursor(PipelineId, Point2D<f32, CSSPixel>),
|
/// display list update is rendered.
|
||||||
|
RefreshCursor(PipelineId),
|
||||||
/// Notifies script of the viewport.
|
/// Notifies script of the viewport.
|
||||||
Viewport(PipelineId, Rect<f32, UnknownUnit>),
|
Viewport(PipelineId, Rect<f32, UnknownUnit>),
|
||||||
/// Requests that the script thread immediately send the constellation the title of a pipeline.
|
/// Requests that the script thread immediately send the constellation the title of a pipeline.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue