diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index 1603a224ed3..c415b8cafee 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -329,23 +329,23 @@ impl WebViewRenderer { } } - pub(crate) fn dispatch_point_input_event(&self, mut event: InputEvent) -> bool { - // Events that do not need to do hit testing are sent directly to the - // constellation to filter down. - let Some(point) = event.point() else { - return false; - }; - - // If we can't find a pipeline to send this event to, we cannot continue. - let Some(result) = self - .global - .borrow() - .hit_test_at_point(point) - .into_iter() - .nth(0) - else { - warn!("Empty hit test result for input event, ignoring."); - return false; + pub(crate) fn dispatch_input_event_with_hit_testing(&self, mut event: InputEvent) -> bool { + let event_point = event.point(); + let hit_test_result = match event_point { + Some(point) => { + let hit_test_result = self + .global + .borrow() + .hit_test_at_point(point) + .into_iter() + .nth(0); + if hit_test_result.is_none() { + warn!("Empty hit test result for input event, ignoring."); + return false; + } + hit_test_result + }, + None => None, }; match event { @@ -353,7 +353,7 @@ impl WebViewRenderer { touch_event.init_sequence_id(self.touch_handler.current_sequence_id); }, InputEvent::MouseMove(_) => { - self.global.borrow_mut().last_mouse_move_position = Some(point); + self.global.borrow_mut().last_mouse_move_position = event_point; }, InputEvent::MouseLeave(_) => { self.global.borrow_mut().last_mouse_move_position = None; @@ -363,7 +363,7 @@ impl WebViewRenderer { } if let Err(error) = self.global.borrow().constellation_sender.send( - EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)), + EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, hit_test_result), ) { warn!("Sending event to constellation failed ({error:?})."); false @@ -438,11 +438,11 @@ impl WebViewRenderer { } } - self.dispatch_point_input_event(event); + self.dispatch_input_event_with_hit_testing(event); } fn send_touch_event(&mut self, event: TouchEvent) -> bool { - self.dispatch_point_input_event(InputEvent::Touch(event)) + self.dispatch_input_event_with_hit_testing(InputEvent::Touch(event)) } pub(crate) fn on_touch_event(&mut self, event: TouchEvent) { @@ -706,13 +706,15 @@ impl WebViewRenderer { /// fn simulate_mouse_click(&mut self, point: DevicePoint) { let button = MouseButton::Left; - self.dispatch_point_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); - self.dispatch_point_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + self.dispatch_input_event_with_hit_testing(InputEvent::MouseMove(MouseMoveEvent::new( + point, + ))); + self.dispatch_input_event_with_hit_testing(InputEvent::MouseButton(MouseButtonEvent::new( MouseButtonAction::Down, button, point, ))); - self.dispatch_point_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + self.dispatch_input_event_with_hit_testing(InputEvent::MouseButton(MouseButtonEvent::new( MouseButtonAction::Up, button, point, diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 400e15f2344..74c21eb87cf 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -2939,34 +2939,8 @@ where } } - let pipeline_id = match &hit_test_result { - Some(hit_test) => hit_test.pipeline_id, - None => { - // If there's no hit test, send to the focused browsing context of the given webview. - let Some(browsing_context_id) = self - .webviews - .get(webview_id) - .map(|webview| webview.focused_browsing_context_id) - else { - warn!("Handling InputEvent for an unknown webview: {webview_id}"); - return; - }; - - let Some(pipeline_id) = self - .browsing_contexts - .get(&browsing_context_id) - .map(|context| context.pipeline_id) - else { - warn!("{browsing_context_id}: Got InputEvent for nonexistent browsing context"); - return; - }; - - pipeline_id - }, - }; - - let Some(pipeline) = self.pipelines.get(&pipeline_id) else { - debug!("Got event for pipeline ({pipeline_id}) after closure"); + let Some(webview) = self.webviews.get_mut(webview_id) else { + warn!("Got input event for unknown WebViewId: {webview_id:?}"); return; }; @@ -2976,13 +2950,7 @@ where active_keyboard_modifiers, event, }; - - if let Err(error) = pipeline - .event_loop - .send(ScriptThreadMessage::SendInputEvent(pipeline_id, event)) - { - self.handle_send_error(pipeline_id, error); - } + webview.forward_input_event(event, &self.pipelines, &self.browsing_contexts); } #[servo_tracing::instrument(skip_all)] diff --git a/components/constellation/constellation_webview.rs b/components/constellation/constellation_webview.rs index 16c19e629fb..14e7cf774dd 100644 --- a/components/constellation/constellation_webview.rs +++ b/components/constellation/constellation_webview.rs @@ -2,9 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use base::id::BrowsingContextId; -use embedder_traits::Theme; +use std::collections::HashMap; +use base::id::{BrowsingContextId, PipelineId}; +use embedder_traits::{InputEvent, MouseLeaveEvent, Theme}; +use euclid::Point2D; +use log::warn; +use script_traits::{ConstellationInputEvent, ScriptThreadMessage}; +use style_traits::CSSPixel; + +use crate::browsingcontext::BrowsingContext; +use crate::pipeline::Pipeline; use crate::session_history::JointSessionHistory; /// The `Constellation`'s view of a `WebView` in the embedding layer. This tracks all of the @@ -15,6 +23,14 @@ pub(crate) struct ConstellationWebView { /// context. pub focused_browsing_context_id: BrowsingContextId, + /// The [`BrowsingContextId`] of the currently hovered browsing context, to use for + /// knowing which frame is currently receiving cursor events. + pub hovered_browsing_context_id: Option, + + /// The last mouse move point in the coordinate space of the Pipeline that it + /// happened int. + pub last_mouse_move_point: Point2D, + /// The joint session history for this webview. pub session_history: JointSessionHistory, @@ -27,6 +43,8 @@ impl ConstellationWebView { pub(crate) fn new(focused_browsing_context_id: BrowsingContextId) -> Self { Self { focused_browsing_context_id, + hovered_browsing_context_id: None, + last_mouse_move_point: Default::default(), session_history: JointSessionHistory::new(), theme: Theme::Light, } @@ -42,4 +60,91 @@ impl ConstellationWebView { pub(crate) fn theme(&self) -> Theme { self.theme } + + fn target_pipeline_id_for_input_event( + &self, + event: &ConstellationInputEvent, + browsing_contexts: &HashMap, + ) -> Option { + if let Some(hit_test_result) = &event.hit_test_result { + return Some(hit_test_result.pipeline_id); + } + + // If there's no hit test, send the event to either the hovered or focused browsing context, + // depending on the event type. + let browsing_context_id = if matches!(event.event, InputEvent::MouseLeave(_)) { + self.hovered_browsing_context_id + .unwrap_or(self.focused_browsing_context_id) + } else { + self.focused_browsing_context_id + }; + + Some(browsing_contexts.get(&browsing_context_id)?.pipeline_id) + } + + pub(crate) fn forward_input_event( + &mut self, + event: ConstellationInputEvent, + pipelines: &HashMap, + browsing_contexts: &HashMap, + ) { + let Some(pipeline_id) = self.target_pipeline_id_for_input_event(&event, browsing_contexts) + else { + warn!("Unknown pipeline for input event. Ignoring."); + return; + }; + let Some(pipeline) = pipelines.get(&pipeline_id) else { + warn!("Unknown pipeline id {pipeline_id:?} for input event. Ignoring."); + 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 synthetic_mouse_leave_event = event.clone(); + synthetic_mouse_leave_event.event = InputEvent::MouseLeave(MouseLeaveEvent { + focus_moving_to_another_iframe: true, + }); + + let _ = pipeline + .event_loop + .send(ScriptThreadMessage::SendInputEvent( + pipeline.id, + synthetic_mouse_leave_event, + )); + }; + + if let InputEvent::MouseLeave(_) = &event.event { + update_hovered_browsing_context(None); + return; + } + + if let InputEvent::MouseMove(_) = &event.event { + update_hovered_browsing_context(Some(pipeline.browsing_context_id)); + self.last_mouse_move_point = event + .hit_test_result + .as_ref() + .expect("MouseMove events should always have hit tests.") + .point_in_viewport; + } + + let _ = pipeline + .event_loop + .send(ScriptThreadMessage::SendInputEvent(pipeline.id, event)); + } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 3e13a2260f4..ea515556b65 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -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, + /// A helper used to process and store data related to input event handling. + event_handler: DocumentEventHandler, /// Caches for the getElement methods id_map: DomRefCell>>>, name_map: DomRefCell>>>, @@ -2001,7 +2004,6 @@ impl Document { pub(crate) unsafe fn handle_mouse_move_event( &self, input_event: &ConstellationInputEvent, - prev_mouse_over_target: &MutNullableDom, 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::() .is_ancestor_of(new_target.upcast::()); @@ -2078,9 +2082,6 @@ impl Document { .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::) { - 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::())); let event_target = DomRoot::from_ref(new_target.upcast::()); @@ -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>) { + 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::() + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::) + .next() + }) { + let status = anchor + .upcast::() + .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::() + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::) + .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::(); + for element in current_hover_target + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::) + { + 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.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()); } } diff --git a/components/script/dom/document_event_handler.rs b/components/script/dom/document_event_handler.rs new file mode 100644 index 00000000000..d79d12b3dd6 --- /dev/null +++ b/components/script/dom/document_event_handler.rs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::Cell; + +use embedder_traits::Cursor; +use euclid::Point2D; +use style_traits::CSSPixel; + +use crate::dom::bindings::root::MutNullableDom; +use crate::dom::types::Element; + +/// The [`DocumentEventHandler`] is a structure responsible for handling events for +/// the [`crate::Document`] and storing data related to event handling. It exists to +/// decrease the size of the [`crate::Document`] structure. +#[derive(Default, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +pub(crate) struct DocumentEventHandler { + /// The element that is currently hovered by the cursor. + pub(crate) current_hover_target: MutNullableDom, + /// The most recent mouse movement point, used for processing `mouseleave` events. + #[no_trace] + pub(crate) most_recent_mousemove_point: Point2D, + /// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered + /// by the cursor. + #[no_trace] + pub(crate) current_cursor: Cell>, +} diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 2901ce7ea20..d94d0e062c8 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -5425,8 +5425,8 @@ impl Element { } pub(crate) fn set_focus_state(&self, value: bool) { - self.set_state(ElementState::FOCUS, value); self.upcast::().dirty(NodeDamage::Other); + self.set_state(ElementState::FOCUS, value); } pub(crate) fn hover_state(&self) -> bool { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 4f91b005642..e5397a8e83d 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -298,6 +298,7 @@ pub(crate) mod dissimilaroriginlocation; pub(crate) mod dissimilaroriginwindow; #[allow(dead_code)] pub(crate) mod document; +pub(crate) mod document_event_handler; pub(crate) mod documentfragment; pub(crate) mod documentorshadowroot; pub(crate) mod documenttype; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 59c29cf47d7..e78ded5ba5f 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -2599,21 +2599,25 @@ impl Window { self.layout().query_elements_from_point(point, flags) } - #[allow(unsafe_code)] pub(crate) fn hit_test_from_input_event( &self, input_event: &ConstellationInputEvent, ) -> Option { - let compositor_hit_test_result = input_event.hit_test_result.as_ref()?; + self.hit_test_from_point_in_viewport( + input_event.hit_test_result.as_ref()?.point_in_viewport, + ) + } + + #[allow(unsafe_code)] + pub(crate) fn hit_test_from_point_in_viewport( + &self, + point_in_frame: Point2D, + ) -> Option { let result = self - .elements_from_point_query( - compositor_hit_test_result.point_in_viewport.cast_unit(), - ElementsFromPointFlags::empty(), - ) + .elements_from_point_query(point_in_frame.cast_unit(), ElementsFromPointFlags::empty()) .into_iter() .nth(0)?; - let point_in_frame = compositor_hit_test_result.point_in_viewport; let point_relative_to_initial_containing_block = point_in_frame + self.scroll_offset().cast_unit(); diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 93adedce430..c357499bb57 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -49,15 +49,14 @@ use devtools_traits::{ }; use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ - EmbedderMsg, FocusSequenceNumber, InputEvent, JavaScriptEvaluationError, - JavaScriptEvaluationId, MediaSessionActionType, MouseButton, MouseButtonAction, - MouseButtonEvent, Theme, ViewportDetails, WebDriverScriptCommand, + FocusSequenceNumber, InputEvent, JavaScriptEvaluationError, JavaScriptEvaluationId, + MediaSessionActionType, MouseButton, MouseButtonAction, MouseButtonEvent, Theme, + ViewportDetails, WebDriverScriptCommand, }; use euclid::Point2D; use euclid::default::Rect; use fonts::{FontContext, SystemFontServiceProxy}; use headers::{HeaderMapExt, LastModified, ReferrerPolicy as ReferrerPolicyHeader}; -use html5ever::{local_name, ns}; use http::header::REFRESH; use hyper_serde::Serde; use ipc_channel::ipc; @@ -115,9 +114,7 @@ use crate::dom::bindings::conversions::{ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomGlobal; -use crate::dom::bindings::root::{ - Dom, DomRoot, MutNullableDom, RootCollection, ThreadLocalStackRoots, -}; +use crate::dom::bindings::root::{Dom, DomRoot, RootCollection, ThreadLocalStackRoots}; use crate::dom::bindings::settings_stack::AutoEntryScript; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::{HashMapTracedValues, JSTraceable}; @@ -130,11 +127,10 @@ use crate::dom::document::{ }; use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; -use crate::dom::htmlanchorelement::HTMLAnchorElement; use crate::dom::htmliframeelement::HTMLIFrameElement; use crate::dom::htmlslotelement::HTMLSlotElement; use crate::dom::mutationobserver::MutationObserver; -use crate::dom::node::{Node, NodeTraits, ShadowIncluding}; +use crate::dom::node::NodeTraits; use crate::dom::servoparser::{ParserContext, ServoParser}; use crate::dom::types::DebuggerGlobalScope; #[cfg(feature = "webgpu")] @@ -253,9 +249,6 @@ pub struct ScriptThread { /// The JavaScript runtime. js_runtime: Rc, - /// The topmost element over the mouse. - topmost_mouse_over_target: MutNullableDom, - /// List of pipelines that have been owned and closed by this script thread. #[no_trace] closed_pipelines: DomRefCell>, @@ -978,7 +971,6 @@ impl ScriptThread { timer_scheduler: Default::default(), microtask_queue, js_runtime, - topmost_mouse_over_target: MutNullableDom::new(Default::default()), closed_pipelines: DomRefCell::new(HashSet::new()), mutation_observer_microtask_queued: Default::default(), mutation_observers: Default::default(), @@ -1053,59 +1045,7 @@ impl ScriptThread { input_event: &ConstellationInputEvent, can_gc: CanGc, ) { - // Get the previous target temporarily - let prev_mouse_over_target = self.topmost_mouse_over_target.get(); - - unsafe { - document.handle_mouse_move_event(input_event, &self.topmost_mouse_over_target, can_gc) - } - - // Short-circuit if nothing changed - if self.topmost_mouse_over_target.get() == prev_mouse_over_target { - return; - } - - let mut state_already_changed = false; - - // Notify Constellation about the topmost anchor mouse over target. - let window = document.window(); - if let Some(target) = self.topmost_mouse_over_target.get() { - if let Some(anchor) = target - .upcast::() - .inclusive_ancestors(ShadowIncluding::No) - .filter_map(DomRoot::downcast::) - .next() - { - let status = anchor - .upcast::() - .get_attribute(&ns!(), &local_name!("href")) - .and_then(|href| { - let value = href.value(); - let url = document.url(); - url.join(&value).map(|url| url.to_string()).ok() - }); - let event = EmbedderMsg::Status(document.webview_id(), status); - window.send_to_embedder(event); - - state_already_changed = true; - } - } - - // We might have to reset the anchor state - if !state_already_changed { - if let Some(target) = prev_mouse_over_target { - if target - .upcast::() - .inclusive_ancestors(ShadowIncluding::No) - .filter_map(DomRoot::downcast::) - .next() - .is_some() - { - let event = EmbedderMsg::Status(window.webview_id(), None); - window.send_to_embedder(event); - } - } - } + unsafe { document.handle_mouse_move_event(input_event, can_gc) } } /// Process compositor events as part of a "update the rendering task". @@ -1131,12 +1071,10 @@ impl ScriptThread { document.handle_mouse_button_event(mouse_button_event, &event, can_gc); }, InputEvent::MouseMove(_) => { - // The event itself is unecessary here, because the point in the viewport is in the hit test. self.process_mouse_move_event(&document, &event, can_gc); }, - InputEvent::MouseLeave(_) => { - self.topmost_mouse_over_target.take(); - document.handle_mouse_leave_event(&event, can_gc); + InputEvent::MouseLeave(mouse_leave_event) => { + document.handle_mouse_leave_event(&event, &mouse_leave_event, can_gc); }, InputEvent::Touch(touch_event) => { let touch_result = document.handle_touch_event(touch_event, &event, can_gc); @@ -3062,13 +3000,6 @@ impl ScriptThread { debug!("{id}: Clearing animations"); document.animations().clear(); - // We don't want to dispatch `mouseout` event pointing to non-existing element - if let Some(target) = self.topmost_mouse_over_target.get() { - if target.upcast::().owner_doc() == document { - self.topmost_mouse_over_target.set(None); - } - } - // We discard the browsing context after requesting layout shut down, // to avoid running layout on detached iframes. let window = document.window(); diff --git a/components/shared/embedder/input_events.rs b/components/shared/embedder/input_events.rs index 372fac2277f..cdb6a2e7892 100644 --- a/components/shared/embedder/input_events.rs +++ b/components/shared/embedder/input_events.rs @@ -43,7 +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::MouseLeave(_) => None, InputEvent::Touch(event) => Some(event.point), InputEvent::Wheel(event) => Some(event.point), InputEvent::Scroll(..) => None, @@ -218,15 +218,9 @@ impl MouseMoveEvent { } } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] pub struct MouseLeaveEvent { - pub point: DevicePoint, -} - -impl MouseLeaveEvent { - pub fn new(point: DevicePoint) -> Self { - Self { point } - } + pub focus_moving_to_another_iframe: bool, } /// The type of input represented by a multi-touch event. diff --git a/ports/servoshell/desktop/headed_window.rs b/ports/servoshell/desktop/headed_window.rs index 1e1c1cbdb7d..39021171945 100644 --- a/ports/servoshell/desktop/headed_window.rs +++ b/ports/servoshell/desktop/headed_window.rs @@ -624,17 +624,17 @@ impl WindowPortsMethods for Window { if webview.rect().contains(point) { webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); } else if webview.rect().contains(previous_point) { - webview.notify_input_event(InputEvent::MouseLeave(MouseLeaveEvent::new( - previous_point, - ))); + webview.notify_input_event(InputEvent::MouseLeave(MouseLeaveEvent::default())); } self.webview_relative_mouse_point.set(point); }, WindowEvent::CursorLeft { .. } => { - let point = self.webview_relative_mouse_point.get(); - if webview.rect().contains(point) { - webview.notify_input_event(InputEvent::MouseLeave(MouseLeaveEvent::new(point))); + if webview + .rect() + .contains(self.webview_relative_mouse_point.get()) + { + webview.notify_input_event(InputEvent::MouseLeave(MouseLeaveEvent::default())); } }, WindowEvent::MouseWheel { delta, .. } => {