mirror of
https://github.com/servo/servo.git
synced 2025-09-30 08:39:16 +01:00
script/compositor: Send mouseleave
events when cursor moves between <iframe>
s (#38539)
Properly send `mouseleave` events when the cursor moves between `<iframe>`s. This allows a better handling of cursor changes and status text updates. Specifically, we do not need to continuously update the cursor and the value can be cached in the `Document`. In addition, status updates can now be sent properly when moving focus between `<iframe>`s. Note that style updates for `:hover` values are still broken, but less so than before. Now the hover state on the `Node` is updated, but for some reason the restyle isn't taking place properly. This maintains the status quo as far as behavior goes when hover moves between `<iframe>`s. This change also adds a helper data structure to `Document` which will eventually be responsible for event handling. Testing: Cursor and status change are currently very hard to test as the API test harness makes this difficult at the moment. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
82ca2b92cd
commit
b75c3feb97
11 changed files with 307 additions and 210 deletions
|
@ -30,8 +30,8 @@ use dom_struct::dom_struct;
|
|||
use embedder_traits::{
|
||||
AllowOrDeny, AnimationState, ContextMenuResult, Cursor, EditingActionEvent, EmbedderMsg,
|
||||
FocusSequenceNumber, ImeEvent, InputEvent, LoadStatus, MouseButton, MouseButtonAction,
|
||||
MouseButtonEvent, ScrollEvent, TouchEvent, TouchEventType, TouchId, UntrustedNodeAddress,
|
||||
WheelEvent,
|
||||
MouseButtonEvent, MouseLeaveEvent, ScrollEvent, TouchEvent, TouchEventType, TouchId,
|
||||
UntrustedNodeAddress, WheelEvent,
|
||||
};
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use euclid::Point2D;
|
||||
|
@ -141,6 +141,7 @@ use crate::dom::cssstylesheet::CSSStyleSheet;
|
|||
use crate::dom::customelementregistry::CustomElementDefinition;
|
||||
use crate::dom::customevent::CustomEvent;
|
||||
use crate::dom::datatransfer::DataTransfer;
|
||||
use crate::dom::document_event_handler::DocumentEventHandler;
|
||||
use crate::dom::documentfragment::DocumentFragment;
|
||||
use crate::dom::documentorshadowroot::{
|
||||
DocumentOrShadowRoot, ServoStylesheetInDocument, StylesheetSource,
|
||||
|
@ -322,6 +323,8 @@ pub(crate) struct Document {
|
|||
#[ignore_malloc_size_of = "defined in selectors"]
|
||||
#[no_trace]
|
||||
quirks_mode: Cell<QuirksMode>,
|
||||
/// A helper used to process and store data related to input event handling.
|
||||
event_handler: DocumentEventHandler,
|
||||
/// Caches for the getElement methods
|
||||
id_map: DomRefCell<HashMapTracedValues<Atom, Vec<Dom<Element>>>>,
|
||||
name_map: DomRefCell<HashMapTracedValues<Atom, Vec<Dom<Element>>>>,
|
||||
|
@ -2001,7 +2004,6 @@ impl Document {
|
|||
pub(crate) unsafe fn handle_mouse_move_event(
|
||||
&self,
|
||||
input_event: &ConstellationInputEvent,
|
||||
prev_mouse_over_target: &MutNullableDom<Element>,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
// Ignore all incoming events without a hit test.
|
||||
|
@ -2021,7 +2023,9 @@ impl Document {
|
|||
return;
|
||||
};
|
||||
|
||||
let target_has_changed = prev_mouse_over_target
|
||||
let target_has_changed = self
|
||||
.event_handler
|
||||
.current_hover_target
|
||||
.get()
|
||||
.as_ref()
|
||||
.is_none_or(|old_target| old_target != &new_target);
|
||||
|
@ -2030,7 +2034,7 @@ impl Document {
|
|||
// dispatch mouseout to the previous one, mouseover to the new one.
|
||||
if target_has_changed {
|
||||
// Dispatch mouseout and mouseleave to previous target.
|
||||
if let Some(old_target) = prev_mouse_over_target.get() {
|
||||
if let Some(old_target) = self.event_handler.current_hover_target.get() {
|
||||
let old_target_is_ancestor_of_new_target = old_target
|
||||
.upcast::<Node>()
|
||||
.is_ancestor_of(new_target.upcast::<Node>());
|
||||
|
@ -2078,9 +2082,6 @@ impl Document {
|
|||
.inclusive_ancestors(ShadowIncluding::No)
|
||||
.filter_map(DomRoot::downcast::<Element>)
|
||||
{
|
||||
if element.hover_state() {
|
||||
break;
|
||||
}
|
||||
element.set_hover_state(true);
|
||||
}
|
||||
|
||||
|
@ -2094,7 +2095,9 @@ impl Document {
|
|||
can_gc,
|
||||
);
|
||||
|
||||
let moving_from = prev_mouse_over_target
|
||||
let moving_from = self
|
||||
.event_handler
|
||||
.current_hover_target
|
||||
.get()
|
||||
.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
|
||||
let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
|
||||
|
@ -2120,56 +2123,107 @@ impl Document {
|
|||
can_gc,
|
||||
);
|
||||
|
||||
// If the target has changed then store the current mouse over target for next frame.
|
||||
if target_has_changed {
|
||||
prev_mouse_over_target.set(Some(&new_target));
|
||||
}
|
||||
self.update_current_hover_target_and_status(Some(new_target));
|
||||
}
|
||||
|
||||
pub(crate) fn set_cursor(&self, cursor: Cursor) {
|
||||
self.send_to_embedder(EmbedderMsg::SetCursor(self.webview_id(), cursor));
|
||||
fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
|
||||
let previous_hover_target = self.event_handler.current_hover_target.get();
|
||||
if previous_hover_target == new_hover_target {
|
||||
return;
|
||||
}
|
||||
|
||||
self.event_handler
|
||||
.current_hover_target
|
||||
.set(new_hover_target.as_deref());
|
||||
|
||||
// If the new hover target is an anchor with a status value, inform the embedder
|
||||
// of the new value.
|
||||
let window = self.window();
|
||||
if let Some(anchor) = new_hover_target.and_then(|new_hover_target| {
|
||||
new_hover_target
|
||||
.upcast::<Node>()
|
||||
.inclusive_ancestors(ShadowIncluding::No)
|
||||
.filter_map(DomRoot::downcast::<HTMLAnchorElement>)
|
||||
.next()
|
||||
}) {
|
||||
let status = anchor
|
||||
.upcast::<Element>()
|
||||
.get_attribute(&ns!(), &local_name!("href"))
|
||||
.and_then(|href| {
|
||||
let value = href.value();
|
||||
let url = self.url();
|
||||
url.join(&value).map(|url| url.to_string()).ok()
|
||||
});
|
||||
window.send_to_embedder(EmbedderMsg::Status(self.webview_id(), status));
|
||||
return;
|
||||
}
|
||||
|
||||
// No state was set above, which means that the new value of the status in the embedder
|
||||
// should be `None`. Set that now. If `previous_hover_target` is `None` that means this
|
||||
// is the first mouse move event we are seeing after getting the cursor. In that case,
|
||||
// we also clear the status.
|
||||
if previous_hover_target.is_none_or(|previous_hover_target| {
|
||||
previous_hover_target
|
||||
.upcast::<Node>()
|
||||
.inclusive_ancestors(ShadowIncluding::No)
|
||||
.filter_map(DomRoot::downcast::<HTMLAnchorElement>)
|
||||
.next()
|
||||
.is_some()
|
||||
}) {
|
||||
window.send_to_embedder(EmbedderMsg::Status(window.webview_id(), None));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub(crate) fn handle_mouse_leave_event(
|
||||
&self,
|
||||
input_event: &ConstellationInputEvent,
|
||||
mouse_leave_event: &MouseLeaveEvent,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
// Ignore all incoming events without a hit test.
|
||||
let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
|
||||
return;
|
||||
};
|
||||
if let Some(current_hover_target) = self.event_handler.current_hover_target.get() {
|
||||
let current_hover_target = current_hover_target.upcast::<Node>();
|
||||
for element in current_hover_target
|
||||
.inclusive_ancestors(ShadowIncluding::No)
|
||||
.filter_map(DomRoot::downcast::<Element>)
|
||||
{
|
||||
element.set_hover_state(false);
|
||||
element.set_active_state(false);
|
||||
}
|
||||
|
||||
self.window()
|
||||
.send_to_embedder(EmbedderMsg::Status(self.webview_id(), None));
|
||||
|
||||
for element in hit_test_result
|
||||
.node
|
||||
.inclusive_ancestors(ShadowIncluding::No)
|
||||
.filter_map(DomRoot::downcast::<Element>)
|
||||
{
|
||||
element.set_hover_state(false);
|
||||
element.set_active_state(false);
|
||||
if let Some(hit_test_result) = self
|
||||
.window()
|
||||
.hit_test_from_point_in_viewport(self.event_handler.most_recent_mousemove_point)
|
||||
{
|
||||
self.fire_mouse_event(
|
||||
current_hover_target.upcast(),
|
||||
FireMouseEventType::Out,
|
||||
EventBubbles::Bubbles,
|
||||
EventCancelable::Cancelable,
|
||||
&hit_test_result,
|
||||
input_event,
|
||||
can_gc,
|
||||
);
|
||||
self.handle_mouse_enter_leave_event(
|
||||
DomRoot::from_ref(current_hover_target),
|
||||
None,
|
||||
FireMouseEventType::Leave,
|
||||
&hit_test_result,
|
||||
input_event,
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.fire_mouse_event(
|
||||
hit_test_result.node.upcast(),
|
||||
FireMouseEventType::Out,
|
||||
EventBubbles::Bubbles,
|
||||
EventCancelable::Cancelable,
|
||||
&hit_test_result,
|
||||
input_event,
|
||||
can_gc,
|
||||
);
|
||||
self.handle_mouse_enter_leave_event(
|
||||
hit_test_result.node.clone(),
|
||||
None,
|
||||
FireMouseEventType::Leave,
|
||||
&hit_test_result,
|
||||
input_event,
|
||||
can_gc,
|
||||
);
|
||||
self.event_handler.current_cursor.set(None);
|
||||
self.event_handler.current_hover_target.set(None);
|
||||
|
||||
// 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.
|
||||
if !mouse_leave_event.focus_moving_to_another_iframe {
|
||||
self.window()
|
||||
.send_to_embedder(EmbedderMsg::Status(self.webview_id(), None));
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_enter_leave_event(
|
||||
|
@ -4123,6 +4177,14 @@ impl Document {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn set_cursor(&self, cursor: Cursor) {
|
||||
if Some(cursor) == self.event_handler.current_cursor.get() {
|
||||
return;
|
||||
}
|
||||
self.event_handler.current_cursor.set(Some(cursor));
|
||||
self.send_to_embedder(EmbedderMsg::SetCursor(self.webview_id(), cursor));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_character_value_key(key: &Key) -> bool {
|
||||
|
@ -4321,6 +4383,7 @@ impl Document {
|
|||
url: DomRefCell::new(url),
|
||||
// https://dom.spec.whatwg.org/#concept-document-quirks
|
||||
quirks_mode: Cell::new(QuirksMode::NoQuirks),
|
||||
event_handler: DocumentEventHandler::default(),
|
||||
id_map: DomRefCell::new(HashMapTracedValues::new()),
|
||||
name_map: DomRefCell::new(HashMapTracedValues::new()),
|
||||
// https://dom.spec.whatwg.org/#concept-document-encoding
|
||||
|
@ -4821,14 +4884,14 @@ impl Document {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn element_state_will_change(&self, el: &Element) {
|
||||
let mut entry = self.ensure_pending_restyle(el);
|
||||
pub(crate) fn element_state_will_change(&self, element: &Element) {
|
||||
let mut entry = self.ensure_pending_restyle(element);
|
||||
if entry.snapshot.is_none() {
|
||||
entry.snapshot = Some(Snapshot::new());
|
||||
}
|
||||
let snapshot = entry.snapshot.as_mut().unwrap();
|
||||
if snapshot.state.is_none() {
|
||||
snapshot.state = Some(el.state());
|
||||
snapshot.state = Some(element.state());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue