diff --git a/Cargo.lock b/Cargo.lock index c09537b7bbb..ff0b2735595 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1390,6 +1390,7 @@ dependencies = [ "ipc-channel", "libc", "log", + "num-traits", "pixels", "profile_traits", "servo-tracing", @@ -4789,6 +4790,7 @@ dependencies = [ "servo_malloc_size_of", "servo_url", "stylo", + "stylo_traits", "webrender_api", ] diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml index 648d60b497f..f988a895edc 100644 --- a/components/compositing/Cargo.toml +++ b/components/compositing/Cargo.toml @@ -31,6 +31,7 @@ gleam = { workspace = true } ipc-channel = { workspace = true } libc = { workspace = true } log = { workspace = true } +num-traits = { workspace = true } pixels = { path = "../pixels" } profile_traits = { workspace = true } servo_allocator = { path = "../allocator" } diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 94130f0167c..2f321c7952c 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -11,13 +11,11 @@ use std::rc::Rc; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +use base::Epoch; use base::cross_process_instant::CrossProcessInstant; use base::id::{PipelineId, WebViewId}; -use base::{Epoch, WebRenderEpochToU16}; use bitflags::bitflags; -use compositing_traits::display_list::{ - CompositorDisplayListInfo, HitTestInfo, ScrollTree, ScrollType, -}; +use compositing_traits::display_list::{CompositorDisplayListInfo, ScrollTree, ScrollType}; use compositing_traits::rendering_context::RenderingContext; use compositing_traits::{ CompositionPipeline, CompositorMsg, ImageUpdate, PipelineExitSource, SendableFrameTree, @@ -27,13 +25,12 @@ use constellation_traits::{EmbedderToConstellationMessage, PaintMetricEvent}; use crossbeam_channel::{Receiver, Sender}; use dpi::PhysicalSize; use embedder_traits::{ - CompositorHitTestResult, Cursor, InputEvent, ShutdownState, UntrustedNodeAddress, - ViewportDetails, + CompositorHitTestResult, Cursor, InputEvent, ShutdownState, ViewportDetails, }; use euclid::{Point2D, Rect, Scale, Size2D, Transform3D}; use ipc_channel::ipc::{self, IpcSharedMemory}; -use libc::c_void; use log::{debug, info, trace, warn}; +use num_traits::cast::FromPrimitive; use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage}; use profile_traits::mem::{ProcessReports, ProfilerRegistration, Report, ReportKind}; use profile_traits::time::{self as profile_time, ProfilerCategory}; @@ -48,10 +45,10 @@ use webrender_api::units::{ }; use webrender_api::{ self, BuiltDisplayList, DirtyRect, DisplayListPayload, DocumentId, Epoch as WebRenderEpoch, - FontInstanceFlags, FontInstanceKey, FontInstanceOptions, FontKey, HitTestFlags, - PipelineId as WebRenderPipelineId, PropertyBinding, ReferenceFrameKind, RenderReasons, - SampledScrollOffset, ScrollLocation, SpaceAndClipInfo, SpatialId, SpatialTreeItemKey, - TransformStyle, + ExternalScrollId, FontInstanceFlags, FontInstanceKey, FontInstanceOptions, FontKey, + HitTestFlags, PipelineId as WebRenderPipelineId, PropertyBinding, ReferenceFrameKind, + RenderReasons, SampledScrollOffset, ScrollLocation, SpaceAndClipInfo, SpatialId, + SpatialTreeItemKey, TransformStyle, }; use crate::InitialCompositorState; @@ -200,10 +197,6 @@ pub(crate) struct PipelineDetails { /// The id of the parent pipeline, if any. pub parent_pipeline_id: Option, - /// The epoch of the most recent display list for this pipeline. Note that this display - /// list might not be displayed, as WebRender processes display lists asynchronously. - pub most_recent_display_list_epoch: Option, - /// Whether animations are running pub animations_running: bool, @@ -213,10 +206,6 @@ pub(crate) struct PipelineDetails { /// Whether to use less resources by stopping animations. pub throttled: bool, - /// Hit test items for this pipeline. This is used to map WebRender hit test - /// information to the full information necessary for Servo. - pub hit_test_items: Vec, - /// The compositor-side [ScrollTree]. This is used to allow finding and scrolling /// nodes in the compositor before forwarding new offsets to WebRender. pub scroll_tree: ScrollTree, @@ -252,12 +241,10 @@ impl PipelineDetails { PipelineDetails { pipeline: None, parent_pipeline_id: None, - most_recent_display_list_epoch: None, viewport_scale: None, animations_running: false, animation_callbacks_running: false, throttled: false, - hit_test_items: Vec::new(), scroll_tree: ScrollTree::default(), first_paint_metric: PaintMetricState::Waiting, first_contentful_paint_metric: PaintMetricState::Waiting, @@ -272,11 +259,6 @@ impl PipelineDetails { } } -pub enum HitTestError { - EpochMismatch, - Others, -} - impl ServoRenderer { pub fn shutdown_state(&self) -> ShutdownState { self.shutdown_state.get() @@ -286,57 +268,33 @@ impl ServoRenderer { &self, point: DevicePoint, details_for_pipeline: impl Fn(PipelineId) -> Option<&'a PipelineDetails>, - ) -> Result { - match self.hit_test_at_point_with_flags_and_pipeline( - point, - HitTestFlags::empty(), - None, - details_for_pipeline, - ) { - Ok(hit_test_results) => hit_test_results - .first() - .cloned() - .ok_or(HitTestError::Others), - Err(error) => Err(error), - } + ) -> Vec { + self.hit_test_at_point_with_flags(point, HitTestFlags::empty(), details_for_pipeline) } // TODO: split this into first half (global) and second half (one for whole compositor, one for webview) - pub(crate) fn hit_test_at_point_with_flags_and_pipeline<'a>( + pub(crate) fn hit_test_at_point_with_flags<'a>( &self, point: DevicePoint, flags: HitTestFlags, - pipeline_id: Option, details_for_pipeline: impl Fn(PipelineId) -> Option<&'a PipelineDetails>, - ) -> Result, HitTestError> { + ) -> Vec { // DevicePoint and WorldPoint are the same for us. let world_point = WorldPoint::from_untyped(point.to_untyped()); - let results = - self.webrender_api - .hit_test(self.webrender_document, pipeline_id, world_point, flags); + let results = self.webrender_api.hit_test( + self.webrender_document, + None, /* pipeline_id */ + world_point, + flags, + ); - let mut epoch_mismatch = false; - let results = results + results .items .iter() .filter_map(|item| { let pipeline_id = item.pipeline.into(); let details = details_for_pipeline(pipeline_id)?; - // If the epoch in the tag does not match the current epoch of the pipeline, - // then the hit test is against an old version of the display list. - match details.most_recent_display_list_epoch { - Some(epoch) => { - if epoch.as_u16() != item.tag.1 { - // It's too early to hit test for now. - // New scene building is in progress. - epoch_mismatch = true; - return None; - } - }, - _ => return None, - } - let offset = details .scroll_tree .scroll_offset(pipeline_id.root_scroll_id()) @@ -344,28 +302,20 @@ impl ServoRenderer { let point_in_initial_containing_block = (item.point_in_viewport + offset).to_untyped(); - let info = &details.hit_test_items[item.tag.0 as usize]; + let external_scroll_id = ExternalScrollId(item.tag.0, item.pipeline); + let cursor = Cursor::from_u16(item.tag.1); + Some(CompositorHitTestResult { pipeline_id, point_in_viewport: Point2D::from_untyped(item.point_in_viewport.to_untyped()), point_relative_to_initial_containing_block: Point2D::from_untyped( point_in_initial_containing_block, ), - point_relative_to_item: Point2D::from_untyped( - item.point_relative_to_item.to_untyped(), - ), - node: UntrustedNodeAddress(info.node as *const c_void), - cursor: info.cursor, - scroll_tree_node: info.scroll_tree_node, + cursor, + external_scroll_id, }) }) - .collect(); - - if epoch_mismatch { - return Err(HitTestError::EpochMismatch); - } - - Ok(results) + .collect() } pub(crate) fn send_transaction(&mut self, transaction: Transaction) { @@ -643,10 +593,11 @@ impl IOCompositor { .global .borrow() .hit_test_at_point(point, details_for_pipeline); - if let Ok(result) = result { + + if let Some(result) = result.first() { self.global .borrow_mut() - .update_cursor_from_hittest(point, &result); + .update_cursor_from_hittest(point, result); } } @@ -768,14 +719,10 @@ impl IOCompositor { return warn!("Could not find WebView for incoming display list"); }; - // WebRender is not ready until we receive "NewWebRenderFrameReady" - webview_renderer.webrender_frame_ready.set(false); let old_scale = webview_renderer.device_pixels_per_page_pixel(); let pipeline_id = display_list_info.pipeline_id; let details = webview_renderer.ensure_pipeline_details(pipeline_id.into()); - details.most_recent_display_list_epoch = Some(display_list_info.epoch); - details.hit_test_items = display_list_info.hit_test_info; details.install_new_scroll_tree(display_list_info.scroll_tree); details.viewport_scale = Some(display_list_info.viewport_details.hidpi_scale_factor); @@ -808,33 +755,6 @@ impl IOCompositor { self.global.borrow_mut().send_transaction(transaction); }, - CompositorMsg::HitTest(pipeline, point, flags, sender) => { - // When a display list is sent to WebRender, it starts scene building in a - // separate thread and then that display list is available for hit testing. - // Without flushing scene building, any hit test we do might be done against - // a previous scene, if the last one we sent hasn't finished building. - // - // TODO(mrobinson): Flushing all scene building is a big hammer here, because - // we might only be interested in a single pipeline. The only other option - // would be to listen to the TransactionNotifier for previous per-pipeline - // transactions, but that isn't easily compatible with the event loop wakeup - // mechanism from libserver. - self.global.borrow().webrender_api.flush_scene_builder(); - - let details_for_pipeline = |pipeline_id| self.details_for_pipeline(pipeline_id); - let result = self - .global - .borrow() - .hit_test_at_point_with_flags_and_pipeline( - point, - flags, - pipeline, - details_for_pipeline, - ) - .unwrap_or_default(); - let _ = sender.send(result); - }, - CompositorMsg::GenerateImageKey(sender) => { let _ = sender.send(self.global.borrow().webrender_api.generate_image_key()); }, @@ -1566,15 +1486,6 @@ impl IOCompositor { }, CompositorMsg::NewWebRenderFrameReady(..) => { found_recomposite_msg = true; - - // Process all pending events - // FIXME: Shouldn't `webview_frame_ready` be stored globally and why can't `pending_frames` - // be used here? - self.webview_renderers.iter().for_each(|webview| { - webview.dispatch_pending_point_input_events(); - webview.webrender_frame_ready.set(true); - }); - true }, _ => true, diff --git a/components/compositing/tracing.rs b/components/compositing/tracing.rs index 1c46205f5ec..09b81eefec0 100644 --- a/components/compositing/tracing.rs +++ b/components/compositing/tracing.rs @@ -42,7 +42,6 @@ mod from_constellation { Self::SendInitialTransaction(..) => target!("SendInitialTransaction"), Self::SendScrollNode(..) => target!("SendScrollNode"), Self::SendDisplayList { .. } => target!("SendDisplayList"), - Self::HitTest(..) => target!("HitTest"), Self::GenerateImageKey(..) => target!("GenerateImageKey"), Self::UpdateImages(..) => target!("UpdateImages"), Self::GenerateFontKeys(..) => target!("GenerateFontKeys"), diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index 27a4761413d..f2ea42932c1 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -2,9 +2,9 @@ * 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, RefCell}; +use std::cell::RefCell; +use std::collections::HashMap; use std::collections::hash_map::{Entry, Keys}; -use std::collections::{HashMap, VecDeque}; use std::rc::Rc; use base::id::{PipelineId, WebViewId}; @@ -27,7 +27,7 @@ use style_traits::{CSSPixel, PinchZoomFactor}; use webrender_api::units::{DeviceIntPoint, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D}; use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation}; -use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer}; +use crate::compositor::{PipelineDetails, ServoRenderer}; use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState}; #[derive(Clone, Copy)] @@ -96,10 +96,6 @@ pub(crate) struct WebViewRenderer { /// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with /// active animations or animation frame callbacks. animating: bool, - /// Pending input events queue. Priavte and only this thread pushes events to it. - pending_point_input_events: RefCell>, - /// WebRender is not ready between `SendDisplayList` and `WebRenderFrameReady` messages. - pub webrender_frame_ready: Cell, /// A [`ViewportDescription`] for this [`WebViewRenderer`], which contains the limitations /// and initial values for zoom derived from the `viewport` meta tag in web content. viewport_description: Option, @@ -135,8 +131,6 @@ impl WebViewRenderer { pinch_zoom: PinchZoomFactor::new(1.0), hidpi_scale_factor: Scale::new(hidpi_scale_factor.0), animating: false, - pending_point_input_events: Default::default(), - webrender_frame_ready: Cell::default(), viewport_description: None, } } @@ -335,55 +329,24 @@ impl WebViewRenderer { } } - pub(crate) fn dispatch_point_input_event(&self, event: InputEvent) -> bool { - self.dispatch_point_input_event_internal(event, true) - } - - pub(crate) fn dispatch_pending_point_input_events(&self) { - while let Some(event) = self.pending_point_input_events.borrow_mut().pop_front() { - // TODO: Add multiple retry later if needed. - self.dispatch_point_input_event_internal(event, false); - } - } - - pub(crate) fn dispatch_point_input_event_internal( - &self, - mut event: InputEvent, - retry_on_error: bool, - ) -> bool { + 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; }; - // Delay the event if the epoch is not synchronized yet (new frame is not ready), - // or hit test result would fail and the event is rejected anyway. - if retry_on_error && - (!self.webrender_frame_ready.get() || - !self.pending_point_input_events.borrow().is_empty()) - { - self.pending_point_input_events - .borrow_mut() - .push_back(event); - return false; - } - // If we can't find a pipeline to send this event to, we cannot continue. let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); - let result = match self + let Some(result) = self .global .borrow() .hit_test_at_point(point, get_pipeline_details) - { - Ok(hit_test_results) => Some(hit_test_results), - Err(HitTestError::EpochMismatch) if retry_on_error => { - self.pending_point_input_events - .borrow_mut() - .push_back(event.clone()); - return false; - }, - _ => None, + .into_iter() + .nth(0) + else { + warn!("Empty hit test result for input event, ignoring."); + return false; }; match event { @@ -394,19 +357,15 @@ impl WebViewRenderer { InputEvent::MouseLeave(_) | InputEvent::MouseMove(_) | InputEvent::Wheel(_) => { - if let Some(ref result) = result { - self.global - .borrow_mut() - .update_cursor_from_hittest(point, result); - } else { - warn!("Not hit test result."); - } + self.global + .borrow_mut() + .update_cursor_from_hittest(point, &result); }, _ => unreachable!("Unexpected input event type: {event:?}"), } if let Err(error) = self.global.borrow().constellation_sender.send( - EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, result), + EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)), ) { warn!("Sending event to constellation failed ({error:?})."); false @@ -895,16 +854,11 @@ impl WebViewRenderer { }; let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); - let hit_test_results = self - .global - .borrow() - .hit_test_at_point_with_flags_and_pipeline( - cursor, - HitTestFlags::FIND_ALL, - None, - get_pipeline_details, - ) - .unwrap_or_default(); + let hit_test_results = self.global.borrow().hit_test_at_point_with_flags( + cursor, + HitTestFlags::FIND_ALL, + get_pipeline_details, + ); // Iterate through all hit test results, processing only the first node of each pipeline. // This is needed to propagate the scroll events from a pipeline representing an iframe to @@ -916,7 +870,7 @@ impl WebViewRenderer { Some(&hit_test_result.pipeline_id) { let scroll_result = pipeline_details.scroll_tree.scroll_node_or_ancestor( - &hit_test_result.scroll_tree_node, + &hit_test_result.external_scroll_id, scroll_location, ScrollType::InputEvents, ); diff --git a/components/layout/display_list/hit_test.rs b/components/layout/display_list/hit_test.rs index 2cffc7d1f30..a6c5adf9e55 100644 --- a/components/layout/display_list/hit_test.rs +++ b/components/layout/display_list/hit_test.rs @@ -6,12 +6,13 @@ use std::collections::HashMap; use app_units::Au; use base::id::ScrollTreeNodeId; -use euclid::{Box2D, Point2D, Point3D}; +use euclid::{Box2D, Point2D, Point3D, Vector2D}; use kurbo::{Ellipse, Shape}; use layout_api::{ElementsFromPointFlags, ElementsFromPointResult}; use style::computed_values::backface_visibility::T as BackfaceVisibility; use style::computed_values::pointer_events::T as PointerEvents; use style::computed_values::visibility::T as Visibility; +use style::properties::ComputedValues; use webrender_api::BorderRadius; use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, RectExt}; @@ -20,7 +21,7 @@ use crate::display_list::stacking_context::StackingContextSection; use crate::display_list::{ StackingContext, StackingContextContent, StackingContextTree, ToWebRender, }; -use crate::fragment_tree::{BoxFragment, Fragment}; +use crate::fragment_tree::Fragment; use crate::geom::PhysicalRect; pub(crate) struct HitTest<'a> { @@ -125,7 +126,7 @@ impl<'a> HitTest<'a> { impl Clip { fn contains(&self, point: LayoutPoint) -> bool { - rounded_rect_contains_point(self.rect, || self.radii, point) + rounded_rect_contains_point(self.rect, &self.radii, point) } } @@ -245,22 +246,10 @@ impl Fragment { return false; }; - let point_in_target; - let transform; - let hit = match self { - Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { - (point_in_target, transform) = - match hit_test.location_in_spatial_node(spatial_node_id) { - Some(point) => point, - None => return false, - }; - box_fragment - .borrow() - .hit_test(point_in_target, containing_block, &transform) - }, - Fragment::Text(text) => { - let text = &*text.borrow(); - let style = text.inline_styles.style.borrow(); + let mut hit_test_fragment_inner = + |style: &ComputedValues, + fragment_rect: PhysicalRect, + border_radius: BorderRadius| { if style.get_inherited_ui().pointer_events == PointerEvents::None { return false; } @@ -268,7 +257,7 @@ impl Fragment { return false; } - (point_in_target, transform) = + let (point_in_spatial_node, transform) = match hit_test.location_in_spatial_node(spatial_node_id) { Some(point) => point, None => return false, @@ -280,68 +269,59 @@ impl Fragment { return false; } - text.rect - .translate(containing_block.origin.to_vector()) - .to_webrender() - .contains(point_in_target) + let fragment_rect = fragment_rect.translate(containing_block.origin.to_vector()); + if !rounded_rect_contains_point( + fragment_rect.to_webrender(), + &border_radius, + point_in_spatial_node, + ) { + return false; + } + + let point_in_target = point_in_spatial_node.cast_unit() - + Vector2D::new( + fragment_rect.origin.x.to_f32_px(), + fragment_rect.origin.y.to_f32_px(), + ); + hit_test.results.push(ElementsFromPointResult { + node: tag.node, + point_in_target, + }); + + !hit_test.flags.contains(ElementsFromPointFlags::FindAll) + }; + + match self { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { + let box_fragment = box_fragment.borrow(); + hit_test_fragment_inner( + &box_fragment.style, + box_fragment.border_rect(), + box_fragment.border_radius(), + ) }, - Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::IFrame(_) | - Fragment::Image(_) | - Fragment::Positioning(_) => return false, - }; - - if !hit { - return false; + Fragment::Text(text) => { + let text = &*text.borrow(); + hit_test_fragment_inner( + &text.inline_styles.style.borrow(), + text.rect, + BorderRadius::zero(), + ) + }, + _ => false, } - - hit_test.results.push(ElementsFromPointResult { - node: tag.node, - point_in_target, - }); - - !hit_test.flags.contains(ElementsFromPointFlags::FindAll) - } -} - -impl BoxFragment { - fn hit_test( - &self, - point_in_fragment: LayoutPoint, - containing_block: &PhysicalRect, - transform: &LayoutTransform, - ) -> bool { - if self.style.get_inherited_ui().pointer_events == PointerEvents::None { - return false; - } - if self.style.get_inherited_box().visibility != Visibility::Visible { - return false; - } - - if self.style.get_box().backface_visibility == BackfaceVisibility::Hidden && - transform.is_backface_visible() - { - return false; - } - - let border_rect = self - .border_rect() - .translate(containing_block.origin.to_vector()) - .to_webrender(); - rounded_rect_contains_point(border_rect, || self.border_radius(), point_in_fragment) } } fn rounded_rect_contains_point( rect: LayoutRect, - border_radius: impl FnOnce() -> BorderRadius, + border_radius: &BorderRadius, point: LayoutPoint, ) -> bool { if !rect.contains(point) { return false; } - let border_radius = border_radius(); if border_radius.is_zero() { return true; } diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index 0d3fac1453c..49fb89a811f 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -6,7 +6,6 @@ use std::cell::{OnceCell, RefCell}; use std::sync::Arc; use app_units::{AU_PER_PX, Au}; -use base::WebRenderEpochToU16; use base::id::ScrollTreeNodeId; use clip::{Clip, ClipId}; use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo}; @@ -72,9 +71,6 @@ use background::BackgroundPainter; pub(crate) use hit_test::HitTest; pub(crate) use stacking_context::*; -// webrender's `ItemTag` is private. -type ItemTag = (u64, u16); -type HitInfo = Option; const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX); pub(crate) struct DisplayListBuilder<'a> { @@ -168,8 +164,6 @@ impl DisplayListBuilder<'_> { ) -> BuiltDisplayList { // Build the rest of the display list which inclues all of the WebRender primitives. let compositor_info = &mut stacking_context_tree.compositor_info; - compositor_info.hit_test_info.clear(); - let mut webrender_display_list_builder = webrender_api::DisplayListBuilder::new(compositor_info.pipeline_id); webrender_display_list_builder.begin(); @@ -396,27 +390,6 @@ impl DisplayListBuilder<'_> { } } - fn hit_info( - &mut self, - style: &ComputedValues, - tag: Option, - auto_cursor: Cursor, - ) -> HitInfo { - use style::computed_values::pointer_events::T as PointerEvents; - - let inherited_ui = style.get_inherited_ui(); - if inherited_ui.pointer_events == PointerEvents::None { - return None; - } - - let hit_test_index = self.compositor_info.add_hit_test_info( - tag?.node.0 as u64, - Some(cursor(inherited_ui.cursor.keyword, auto_cursor)), - self.current_scroll_node_id, - ); - Some((hit_test_index as u64, self.compositor_info.epoch.as_u16())) - } - /// Draw highlights around the node that is currently hovered in the devtools. fn paint_dom_inspector_highlight(&mut self) { let Some(highlight) = self @@ -616,7 +589,6 @@ impl Fragment { self.maybe_push_hit_test_for_style_and_tag( builder, &positioning_fragment.style, - positioning_fragment.base.tag, rect, Cursor::Default, ); @@ -706,24 +678,20 @@ impl Fragment { &self, builder: &mut DisplayListBuilder, style: &ComputedValues, - tag: Option, rect: PhysicalRect, cursor: Cursor, ) { - let hit_info = builder.hit_info(style, tag, cursor); - let hit_info = match hit_info { - Some(hit_info) => hit_info, - None => return, - }; - let clip_chain_id = builder.clip_chain_id(builder.current_clip_id); let spatial_id = builder.spatial_id(builder.current_scroll_node_id); + let external_scroll_id = builder + .compositor_info + .external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id); builder.wr().push_hit_test( rect.to_webrender(), clip_chain_id, spatial_id, style.get_webrender_primitive_flags(), - hit_info, + (external_scroll_id.0, cursor as u16), /* tag */ ); } @@ -756,13 +724,7 @@ impl Fragment { } let parent_style = fragment.inline_styles.style.borrow(); - self.maybe_push_hit_test_for_style_and_tag( - builder, - &parent_style, - fragment.base.tag, - rect, - Cursor::Text, - ); + self.maybe_push_hit_test_for_style_and_tag(builder, &parent_style, rect, Cursor::Text); let color = parent_style.clone_color(); let font_metrics = &fragment.font_metrics; @@ -1116,15 +1078,13 @@ impl<'a> BuilderForBoxFragment<'a> { } fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) { - let hit_info = builder.hit_info( - &self.fragment.style, - self.fragment.base.tag, + let cursor = cursor( + self.fragment.style.get_inherited_ui().cursor.keyword, Cursor::Default, ); - let hit_info = match hit_info { - Some(hit_info) => hit_info, - None => return, - }; + let external_scroll_node_id = builder + .compositor_info + .external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id); let mut common = builder.common_properties(rect, &self.fragment.style); if let Some(clip_chain_id) = self.border_edge_clip(builder, false) { @@ -1135,7 +1095,7 @@ impl<'a> BuilderForBoxFragment<'a> { common.clip_chain_id, common.spatial_id, common.flags, - hit_info, + (external_scroll_node_id.0, cursor as u16), /* tag */ ); } @@ -1902,17 +1862,25 @@ pub(super) fn compute_margin_box_radius( impl BoxFragment { fn border_radius(&self) -> BorderRadius { - let resolve = - |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px(); + let border = self.style.get_border(); + if border.border_top_left_radius.0.is_zero() && + border.border_top_right_radius.0.is_zero() && + border.border_bottom_right_radius.0.is_zero() && + border.border_bottom_left_radius.0.is_zero() + { + return BorderRadius::zero(); + } let border_rect = self.border_rect(); + let resolve = + |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px(); let corner = |corner: &style::values::computed::BorderCornerRadius| { Size2D::new( resolve(&corner.0.width.0, border_rect.size.width), resolve(&corner.0.height.0, border_rect.size.height), ) }; - let border = self.style.get_border(); + let mut radius = wr::BorderRadius { top_left: corner(&border.border_top_left_radius), top_right: corner(&border.border_top_right_radius), diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 88f8b1c82c1..96fc13eb499 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -28,10 +28,10 @@ use data_url::mime::Mime; use devtools_traits::ScriptToDevtoolsControlMsg; use dom_struct::dom_struct; use embedder_traits::{ - AllowOrDeny, AnimationState, CompositorHitTestResult, ContextMenuResult, EditingActionEvent, - EmbedderMsg, FocusSequenceNumber, ImeEvent, InputEvent, LoadStatus, MouseButton, - MouseButtonAction, MouseButtonEvent, ScrollEvent, TouchEvent, TouchEventType, TouchId, - UntrustedNodeAddress, WheelEvent, + AllowOrDeny, AnimationState, ContextMenuResult, EditingActionEvent, EmbedderMsg, + FocusSequenceNumber, ImeEvent, InputEvent, LoadStatus, MouseButton, MouseButtonAction, + MouseButtonEvent, ScrollEvent, TouchEvent, TouchEventType, TouchId, UntrustedNodeAddress, + WheelEvent, }; use encoding_rs::{Encoding, UTF_8}; use euclid::Point2D; @@ -172,6 +172,7 @@ use crate::dom::htmlinputelement::HTMLInputElement; use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; use crate::dom::htmltextareaelement::HTMLTextAreaElement; use crate::dom::htmltitleelement::HTMLTitleElement; +use crate::dom::inputevent::HitTestResult; use crate::dom::intersectionobserver::IntersectionObserver; use crate::dom::keyboardevent::KeyboardEvent; use crate::dom::location::{Location, NavigationType}; @@ -1542,7 +1543,6 @@ impl Document { } } - #[allow(unsafe_code)] pub(crate) fn handle_mouse_button_event( &self, event: MouseButtonEvent, @@ -1550,17 +1550,17 @@ impl Document { can_gc: CanGc, ) { // Ignore all incoming events without a hit test. - let Some(hit_test_result) = &input_event.hit_test_result else { + let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else { return; }; debug!( "{:?}: at {:?}", - event.action, hit_test_result.point_in_viewport + event.action, hit_test_result.point_in_frame ); - let node = unsafe { node::from_untrusted_node_address(hit_test_result.node) }; - let Some(el) = node + let Some(el) = hit_test_result + .node .inclusive_ancestors(ShadowIncluding::Yes) .filter_map(DomRoot::downcast::) .next() @@ -1591,7 +1591,7 @@ impl Document { event, input_event.pressed_mouse_buttons, &self.window, - hit_test_result, + &hit_test_result, input_event.active_keyboard_modifiers, can_gc, )); @@ -1626,13 +1626,13 @@ impl Document { if self.focus_transaction.borrow().is_some() { self.commit_focus_transaction(FocusInitiator::Local, can_gc); } - self.maybe_fire_dblclick(node, hit_test_result, input_event, can_gc); + self.maybe_fire_dblclick(node, &hit_test_result, input_event, can_gc); } // When the contextmenu event is triggered by right mouse button // the contextmenu event MUST be dispatched after the mousedown event. if let (MouseButtonAction::Down, MouseButton::Right) = (event.action, event.button) { - self.maybe_show_context_menu(node.upcast(), hit_test_result, input_event, can_gc); + self.maybe_show_context_menu(node.upcast(), &hit_test_result, input_event, can_gc); } } @@ -1640,7 +1640,7 @@ impl Document { fn maybe_show_context_menu( &self, target: &EventTarget, - hit_test_result: &CompositorHitTestResult, + hit_test_result: &HitTestResult, input_event: &ConstellationInputEvent, can_gc: CanGc, ) { @@ -1652,8 +1652,8 @@ impl Document { EventCancelable::Cancelable, // cancelable Some(&self.window), // view 0, // detail - hit_test_result.point_in_viewport.to_i32(), - hit_test_result.point_in_viewport.to_i32(), + hit_test_result.point_in_frame.to_i32(), + hit_test_result.point_in_frame.to_i32(), hit_test_result .point_relative_to_initial_containing_block .to_i32(), @@ -1698,13 +1698,13 @@ impl Document { fn maybe_fire_dblclick( &self, target: &Node, - hit_test_result: &CompositorHitTestResult, + hit_test_result: &HitTestResult, input_event: &ConstellationInputEvent, can_gc: CanGc, ) { // https://w3c.github.io/uievents/#event-type-dblclick let now = Instant::now(); - let point_in_viewport = hit_test_result.point_in_viewport; + let point_in_frame = hit_test_result.point_in_frame; let opt = self.last_click_info.borrow_mut().take(); if let Some((last_time, last_pos)) = opt { @@ -1713,7 +1713,7 @@ impl Document { let DBL_CLICK_DIST_THRESHOLD = pref!(dom_document_dblclick_dist) as u64; // Calculate distance between this click and the previous click. - let line = point_in_viewport - last_pos; + let line = point_in_frame - last_pos; let dist = (line.dot(line) as f64).sqrt(); if now.duration_since(last_time) < DBL_CLICK_TIMEOUT && @@ -1729,8 +1729,8 @@ impl Document { EventCancelable::Cancelable, Some(&self.window), click_count, - point_in_viewport.to_i32(), - point_in_viewport.to_i32(), + point_in_frame.to_i32(), + point_in_frame.to_i32(), hit_test_result .point_relative_to_initial_containing_block .to_i32(), @@ -1750,7 +1750,7 @@ impl Document { } // Update last_click_info with the time and position of the click. - *self.last_click_info.borrow_mut() = Some((now, point_in_viewport)); + *self.last_click_info.borrow_mut() = Some((now, point_in_frame)); } #[allow(clippy::too_many_arguments)] @@ -1760,7 +1760,7 @@ impl Document { event_name: FireMouseEventType, can_bubble: EventBubbles, cancelable: EventCancelable, - hit_test_result: &CompositorHitTestResult, + hit_test_result: &HitTestResult, input_event: &ConstellationInputEvent, can_gc: CanGc, ) { @@ -1771,8 +1771,8 @@ impl Document { cancelable, Some(&self.window), 0i32, - hit_test_result.point_in_viewport.to_i32(), - hit_test_result.point_in_viewport.to_i32(), + hit_test_result.point_in_frame.to_i32(), + hit_test_result.point_in_frame.to_i32(), hit_test_result .point_relative_to_initial_containing_block .to_i32(), @@ -2004,12 +2004,12 @@ impl Document { can_gc: CanGc, ) { // Ignore all incoming events without a hit test. - let Some(hit_test_result) = &input_event.hit_test_result else { + let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else { return; }; - let node = unsafe { node::from_untrusted_node_address(hit_test_result.node) }; - let Some(new_target) = node + let Some(new_target) = hit_test_result + .node .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::) .next() @@ -2049,7 +2049,7 @@ impl Document { FireMouseEventType::Out, EventBubbles::Bubbles, EventCancelable::Cancelable, - hit_test_result, + &hit_test_result, input_event, can_gc, ); @@ -2061,7 +2061,7 @@ impl Document { event_target, moving_into, FireMouseEventType::Leave, - hit_test_result, + &hit_test_result, input_event, can_gc, ); @@ -2085,7 +2085,7 @@ impl Document { FireMouseEventType::Over, EventBubbles::Bubbles, EventCancelable::Cancelable, - hit_test_result, + &hit_test_result, input_event, can_gc, ); @@ -2098,7 +2098,7 @@ impl Document { event_target, moving_from, FireMouseEventType::Enter, - hit_test_result, + &hit_test_result, input_event, can_gc, ); @@ -2111,7 +2111,7 @@ impl Document { FireMouseEventType::Move, EventBubbles::Bubbles, EventCancelable::Cancelable, - hit_test_result, + &hit_test_result, input_event, can_gc, ); @@ -2129,15 +2129,15 @@ impl Document { can_gc: CanGc, ) { // Ignore all incoming events without a hit test. - let Some(hit_test_result) = &input_event.hit_test_result else { + let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) 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 + for element in hit_test_result + .node .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::) { @@ -2146,19 +2146,19 @@ impl Document { } self.fire_mouse_event( - node.upcast(), + hit_test_result.node.upcast(), FireMouseEventType::Out, EventBubbles::Bubbles, EventCancelable::Cancelable, - hit_test_result, + &hit_test_result, input_event, can_gc, ); self.handle_mouse_enter_leave_event( - node, + hit_test_result.node.clone(), None, FireMouseEventType::Leave, - hit_test_result, + &hit_test_result, input_event, can_gc, ); @@ -2169,7 +2169,7 @@ impl Document { event_target: DomRoot, related_target: Option>, event_type: FireMouseEventType, - hit_test_result: &CompositorHitTestResult, + hit_test_result: &HitTestResult, input_event: &ConstellationInputEvent, can_gc: CanGc, ) { @@ -2224,12 +2224,12 @@ impl Document { can_gc: CanGc, ) { // Ignore all incoming events without a hit test. - let Some(hit_test_result) = &input_event.hit_test_result else { + let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else { return; }; - let node = unsafe { node::from_untrusted_node_address(hit_test_result.node) }; - let Some(el) = node + let Some(el) = hit_test_result + .node .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::) .next() @@ -2243,7 +2243,7 @@ impl Document { "{}: on {:?} at {:?}", wheel_event_type_string, node.debug_str(), - hit_test_result.point_in_viewport + hit_test_result.point_in_frame ); // https://w3c.github.io/uievents/#event-wheelevents @@ -2254,8 +2254,8 @@ impl Document { EventCancelable::Cancelable, Some(&self.window), 0i32, - hit_test_result.point_in_viewport.to_i32(), - hit_test_result.point_in_viewport.to_i32(), + hit_test_result.point_in_frame.to_i32(), + hit_test_result.point_in_frame.to_i32(), hit_test_result .point_relative_to_initial_containing_block .to_i32(), @@ -2286,11 +2286,11 @@ impl Document { pub(crate) fn handle_touch_event( &self, event: TouchEvent, - hit_test_result: Option, + input_event: &ConstellationInputEvent, can_gc: CanGc, ) -> TouchEventResult { // Ignore all incoming events without a hit test. - let Some(hit_test_result) = hit_test_result else { + let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else { self.update_active_touch_points_when_early_return(event); return TouchEventResult::Forwarded; }; @@ -2303,8 +2303,8 @@ impl Document { TouchEventType::Cancel => "touchcancel", }; - let node = unsafe { node::from_untrusted_node_address(hit_test_result.node) }; - let Some(el) = node + let Some(el) = hit_test_result + .node .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::) .next() @@ -2316,12 +2316,12 @@ impl Document { let target = DomRoot::upcast::(el); let window = &*self.window; - let client_x = Finite::wrap(hit_test_result.point_in_viewport.x as f64); - let client_y = Finite::wrap(hit_test_result.point_in_viewport.y as f64); + let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64); + let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64); let page_x = - Finite::wrap(hit_test_result.point_in_viewport.x as f64 + window.PageXOffset() as f64); + Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64); let page_y = - Finite::wrap(hit_test_result.point_in_viewport.y as f64 + window.PageYOffset() as f64); + Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64); let touch = Touch::new( window, identifier, &target, client_x, diff --git a/components/script/dom/documentorshadowroot.rs b/components/script/dom/documentorshadowroot.rs index 18e4f93c2f4..25af818f7c3 100644 --- a/components/script/dom/documentorshadowroot.rs +++ b/components/script/dom/documentorshadowroot.rs @@ -8,7 +8,7 @@ use std::fmt; use embedder_traits::UntrustedNodeAddress; use js::rust::HandleValue; -use layout_api::{ElementsFromPointFlags, ElementsFromPointResult, QueryMsg}; +use layout_api::ElementsFromPointFlags; use script_bindings::error::{Error, ErrorResult}; use script_bindings::script_runtime::JSContext; use servo_arc::Arc; @@ -137,15 +137,6 @@ impl DocumentOrShadowRoot { } } - pub(crate) fn query_elements_from_point( - &self, - point: LayoutPoint, - flags: ElementsFromPointFlags, - ) -> Vec { - self.window.layout_reflow(QueryMsg::ElementsFromPoint); - self.window.layout().query_elements_from_point(point, flags) - } - #[allow(unsafe_code)] // https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint pub(crate) fn element_from_point( @@ -168,7 +159,8 @@ impl DocumentOrShadowRoot { } match self - .query_elements_from_point(LayoutPoint::new(x, y), ElementsFromPointFlags::empty()) + .window + .elements_from_point_query(LayoutPoint::new(x, y), ElementsFromPointFlags::empty()) .first() { Some(result) => { @@ -218,8 +210,9 @@ impl DocumentOrShadowRoot { } // Step 1 and Step 3 - let nodes = - self.query_elements_from_point(LayoutPoint::new(x, y), ElementsFromPointFlags::FindAll); + let nodes = self + .window + .elements_from_point_query(LayoutPoint::new(x, y), ElementsFromPointFlags::FindAll); let mut elements: Vec> = nodes .iter() .flat_map(|result| { diff --git a/components/script/dom/inputevent.rs b/components/script/dom/inputevent.rs index 56933c59d6d..4fc57d1d6d8 100644 --- a/components/script/dom/inputevent.rs +++ b/components/script/dom/inputevent.rs @@ -3,7 +3,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use dom_struct::dom_struct; +use euclid::Point2D; use js::rust::HandleObject; +use style_traits::CSSPixel; use crate::dom::bindings::codegen::Bindings::InputEventBinding::{self, InputEventMethods}; use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEvent_Binding::UIEventMethods; @@ -11,6 +13,7 @@ use crate::dom::bindings::error::Fallible; use crate::dom::bindings::reflector::reflect_dom_object_with_proto; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; +use crate::dom::node::Node; use crate::dom::uievent::UIEvent; use crate::dom::window::Window; use crate::script_runtime::CanGc; @@ -91,3 +94,12 @@ impl InputEventMethods for InputEvent { self.uievent.IsTrusted() } } + +/// A [`HitTestResult`] that is the result of doing a hit test based on a less-fine-grained +/// `CompositorHitTestResult` against our current layout. +pub(crate) struct HitTestResult { + pub node: DomRoot, + pub point_in_node: Point2D, + pub point_in_frame: Point2D, + pub point_relative_to_initial_containing_block: Point2D, +} diff --git a/components/script/dom/mouseevent.rs b/components/script/dom/mouseevent.rs index c0938ed39f8..d66209d6339 100644 --- a/components/script/dom/mouseevent.rs +++ b/components/script/dom/mouseevent.rs @@ -6,7 +6,6 @@ use std::cell::Cell; use std::default::Default; use dom_struct::dom_struct; -use embedder_traits::CompositorHitTestResult; use euclid::Point2D; use js::rust::HandleObject; use keyboard_types::Modifiers; @@ -25,6 +24,7 @@ use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; +use crate::dom::inputevent::HitTestResult; use crate::dom::node::Node; use crate::dom::uievent::UIEvent; use crate::dom::window::Window; @@ -226,7 +226,7 @@ impl MouseEvent { event: embedder_traits::MouseButtonEvent, pressed_mouse_buttons: u16, window: &Window, - hit_test_result: &CompositorHitTestResult, + hit_test_result: &HitTestResult, modifiers: Modifiers, can_gc: CanGc, ) -> DomRoot { @@ -236,7 +236,7 @@ impl MouseEvent { embedder_traits::MouseButtonAction::Down => "mousedown", }; - let client_point = hit_test_result.point_in_viewport.to_i32(); + let client_point = hit_test_result.point_in_frame.to_i32(); let page_point = hit_test_result .point_relative_to_initial_containing_block .to_i32(); @@ -256,7 +256,7 @@ impl MouseEvent { event.button.into(), pressed_mouse_buttons, None, - Some(hit_test_result.point_relative_to_item), + Some(hit_test_result.point_in_node), can_gc, ); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index f487ba2b16a..c62d17583bf 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -8,6 +8,7 @@ use std::cmp; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::default::Default; +use std::ffi::c_void; use std::io::{Write, stderr, stdout}; use std::rc::Rc; use std::sync::{Arc, Mutex}; @@ -33,8 +34,8 @@ use dom_struct::dom_struct; use embedder_traits::user_content_manager::{UserContentManager, UserScript}; use embedder_traits::{ AlertResponse, ConfirmResponse, EmbedderMsg, GamepadEvent, GamepadSupportedHapticEffects, - GamepadUpdateType, PromptResponse, SimpleDialog, Theme, ViewportDetails, WebDriverJSError, - WebDriverJSResult, WebDriverLoadStatus, + GamepadUpdateType, PromptResponse, SimpleDialog, Theme, UntrustedNodeAddress, ViewportDetails, + WebDriverJSError, WebDriverJSResult, WebDriverLoadStatus, }; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as UntypedSize2D}; use euclid::{Point2D, Scale, Size2D, Vector2D}; @@ -51,9 +52,10 @@ use js::rust::{ MutableHandleValue, }; use layout_api::{ - FragmentType, Layout, PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg, - ReflowGoal, ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason, - TrustedNodeAddress, combine_id_with_fragment_type, + ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout, PendingImage, + PendingImageState, PendingRasterizationImage, QueryMsg, ReflowGoal, ReflowPhasesRun, + ReflowRequest, ReflowRequestRestyle, RestyleReason, TrustedNodeAddress, + combine_id_with_fragment_type, }; use malloc_size_of::MallocSizeOf; use media::WindowGLContext; @@ -72,7 +74,7 @@ use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMe use script_bindings::conversions::SafeToJSValConvertible; use script_bindings::interfaces::WindowHelpers; use script_bindings::root::Root; -use script_traits::ScriptThreadMessage; +use script_traits::{ConstellationInputEvent, ScriptThreadMessage}; use selectors::attr::CaseSensitivity; use servo_arc::Arc as ServoArc; use servo_config::{opts, pref}; @@ -88,7 +90,7 @@ use style_traits::CSSPixel; use stylo_atoms::Atom; use url::Position; use webrender_api::ExternalScrollId; -use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel}; +use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint}; use super::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions; use super::bindings::trace::HashMapTracedValues; @@ -138,6 +140,7 @@ use crate::dom::history::History; use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; use crate::dom::htmliframeelement::HTMLIFrameElement; use crate::dom::idbfactory::IDBFactory; +use crate::dom::inputevent::HitTestResult; use crate::dom::location::Location; use crate::dom::medialist::MediaList; use crate::dom::mediaquerylist::{MediaQueryList, MediaQueryListMatchState}; @@ -2589,6 +2592,41 @@ impl Window { .query_text_indext(node.to_opaque(), point_in_node) } + pub(crate) fn elements_from_point_query( + &self, + point: LayoutPoint, + flags: ElementsFromPointFlags, + ) -> Vec { + self.layout_reflow(QueryMsg::ElementsFromPoint); + 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()?; + let result = self + .elements_from_point_query( + compositor_hit_test_result.point_in_viewport.cast_unit(), + ElementsFromPointFlags::empty(), + ) + .into_iter() + .nth(0)?; + + // SAFETY: This is safe because `Window::query_elements_from_point` has ensured that + // layout has run and any OpaqueNodes that no longer refer to real nodes are gone. + let address = UntrustedNodeAddress(result.node.0 as *const c_void); + Some(HitTestResult { + node: unsafe { from_untrusted_node_address(address) }, + point_in_node: result.point_in_target, + point_in_frame: compositor_hit_test_result.point_in_viewport, + point_relative_to_initial_containing_block: compositor_hit_test_result + .point_relative_to_initial_containing_block, + }) + } + #[allow(unsafe_code)] pub(crate) fn init_window_proxy(&self, window_proxy: &WindowProxy) { assert!(self.window_proxy.get().is_none()); diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 6e1766ef301..7fb5d33192a 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1137,8 +1137,7 @@ impl ScriptThread { document.handle_mouse_leave_event(&event, can_gc); }, InputEvent::Touch(touch_event) => { - let touch_result = - document.handle_touch_event(touch_event, event.hit_test_result, can_gc); + let touch_result = document.handle_touch_event(touch_event, &event, can_gc); if let (TouchEventResult::Processed(handled), true) = (touch_result, touch_event.is_cancelable()) { diff --git a/components/shared/compositing/display_list.rs b/components/shared/compositing/display_list.rs index 57c3944165d..b2c93271e6e 100644 --- a/components/shared/compositing/display_list.rs +++ b/components/shared/compositing/display_list.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use base::id::ScrollTreeNodeId; use base::print_tree::PrintTree; use bitflags::bitflags; -use embedder_traits::{Cursor, ViewportDetails}; +use embedder_traits::ViewportDetails; use euclid::{SideOffsets2D, Transform3D}; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; @@ -57,20 +57,6 @@ pub struct AxesScrollSensitivity { pub y: ScrollType, } -/// Information that Servo keeps alongside WebRender display items -/// in order to add more context to hit test results. -#[derive(Debug, Deserialize, PartialEq, Serialize)] -pub struct HitTestInfo { - /// The id of the node of this hit test item. - pub node: u64, - - /// The cursor of this node's hit test item. - pub cursor: Option, - - /// The id of the [ScrollTree] associated with this hit test item. - pub scroll_tree_node: ScrollTreeNodeId, -} - #[derive(Debug, Deserialize, Serialize)] pub enum SpatialTreeNodeInfo { ReferenceFrame(ReferenceFrameNodeInfo), @@ -514,17 +500,33 @@ impl ScrollTree { }) } - /// Scroll the given scroll node on this scroll tree. If the node cannot be scrolled, - /// because it isn't a scrollable node or it's already scrolled to the maximum scroll + fn node_with_external_scroll_node_id( + &self, + external_id: &ExternalScrollId, + ) -> Option { + self.nodes + .iter() + .enumerate() + .find_map(|(index, node)| match &node.info { + SpatialTreeNodeInfo::Scroll(info) if info.external_id == *external_id => { + Some(ScrollTreeNodeId { index }) + }, + _ => None, + }) + } + + /// Scroll the scroll node with the given [`ExternalScrollId`] on this scroll tree. If + /// the node cannot be scrolled, because it's already scrolled to the maximum scroll /// extent, try to scroll an ancestor of this node. Returns the node scrolled and the /// new offset if a scroll was performed, otherwise returns None. pub fn scroll_node_or_ancestor( &mut self, - scroll_node_id: &ScrollTreeNodeId, + external_id: &ExternalScrollId, scroll_location: ScrollLocation, context: ScrollType, ) -> Option<(ExternalScrollId, LayoutVector2D)> { - let result = self.scroll_node_or_ancestor_inner(scroll_node_id, scroll_location, context); + let scroll_node_id = self.node_with_external_scroll_node_id(external_id)?; + let result = self.scroll_node_or_ancestor_inner(&scroll_node_id, scroll_location, context); if result.is_some() { self.invalidate_cached_transforms(); } @@ -713,6 +715,22 @@ impl ScrollTree { }; root_node.invalidate_cached_transforms(self, false /* ancestors_invalid */); } + + fn external_scroll_id_for_scroll_tree_node( + &self, + id: ScrollTreeNodeId, + ) -> Option { + let mut maybe_node = Some(self.get_node(&id)); + + while let Some(node) = maybe_node { + if let Some(external_scroll_id) = node.external_id() { + return Some(external_scroll_id); + } + maybe_node = node.parent.map(|id| self.get_node(&id)); + } + + None + } } /// In order to pretty print the [ScrollTree] structure, we are converting @@ -787,11 +805,6 @@ pub struct CompositorDisplayListInfo { /// The epoch of the display list. pub epoch: Epoch, - /// An array of `HitTestInfo` which is used to store information - /// to assist the compositor to take various actions (set the cursor, - /// scroll without layout) using a WebRender hit test result. - pub hit_test_info: Vec, - /// A ScrollTree used by the compositor to scroll the contents of the /// display list. pub scroll_tree: ScrollTree, @@ -856,7 +869,6 @@ impl CompositorDisplayListInfo { viewport_details, content_size, epoch, - hit_test_info: Default::default(), scroll_tree, root_reference_frame_id, root_scroll_node_id, @@ -865,27 +877,12 @@ impl CompositorDisplayListInfo { } } - /// Add or re-use a duplicate HitTestInfo entry in this `CompositorHitTestInfo` - /// and return the index. - pub fn add_hit_test_info( - &mut self, - node: u64, - cursor: Option, - scroll_tree_node: ScrollTreeNodeId, - ) -> usize { - let hit_test_info = HitTestInfo { - node, - cursor, - scroll_tree_node, - }; - - if let Some(last) = self.hit_test_info.last() { - if hit_test_info == *last { - return self.hit_test_info.len() - 1; - } - } - - self.hit_test_info.push(hit_test_info); - self.hit_test_info.len() - 1 + pub fn external_scroll_id_for_scroll_tree_node( + &self, + id: ScrollTreeNodeId, + ) -> ExternalScrollId { + self.scroll_tree + .external_scroll_id_for_scroll_tree_node(id) + .unwrap_or(ExternalScrollId(0, self.pipeline_id)) } } diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 2c6ab5fd225..b03a803886f 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -25,17 +25,17 @@ use std::sync::{Arc, Mutex}; use bitflags::bitflags; use display_list::CompositorDisplayListInfo; -use embedder_traits::{CompositorHitTestResult, ScreenGeometry}; +use embedder_traits::ScreenGeometry; use euclid::default::Size2D as UntypedSize2D; use ipc_channel::ipc::{self, IpcSharedMemory}; use profile_traits::mem::{OpaqueSender, ReportsChan}; use serde::{Deserialize, Serialize}; -use webrender_api::units::{DevicePoint, LayoutVector2D, TexelRect}; +use webrender_api::units::{LayoutVector2D, TexelRect}; use webrender_api::{ BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData, ExternalImageHandler, ExternalImageId, ExternalImageSource, ExternalScrollId, - FontInstanceFlags, FontInstanceKey, FontKey, HitTestFlags, ImageData, ImageDescriptor, - ImageKey, NativeFontHandle, PipelineId as WebRenderPipelineId, + FontInstanceFlags, FontInstanceKey, FontKey, ImageData, ImageDescriptor, ImageKey, + NativeFontHandle, PipelineId as WebRenderPipelineId, }; use crate::viewport_description::ViewportDescription; @@ -110,14 +110,6 @@ pub enum CompositorMsg { /// An [ipc::IpcBytesReceiver] used to send the raw data of the display list. display_list_receiver: ipc::IpcBytesReceiver, }, - /// Perform a hit test operation. The result will be returned via - /// the provided channel sender. - HitTest( - Option, - DevicePoint, - HitTestFlags, - IpcSender>, - ), /// Create a new image key. The result will be returned via the /// provided channel sender. GenerateImageKey(IpcSender), @@ -245,21 +237,6 @@ impl CrossProcessCompositorApi { } } - /// Perform a hit test operation. Blocks until the operation is complete and - /// and a result is available. - pub fn hit_test( - &self, - pipeline: Option, - point: DevicePoint, - flags: HitTestFlags, - ) -> Vec { - let (sender, receiver) = ipc::channel().unwrap(); - self.0 - .send(CompositorMsg::HitTest(pipeline, point, flags, sender)) - .expect("error sending hit test"); - receiver.recv().expect("error receiving hit test result") - } - /// Create a new image key. Blocks until the key is available. pub fn generate_image_key_blocking(&self) -> Option { let (sender, receiver) = ipc::channel().unwrap(); diff --git a/components/shared/compositing/tests/compositor.rs b/components/shared/compositing/tests/compositor.rs index 48848fa29af..39de4879f78 100644 --- a/components/shared/compositing/tests/compositor.rs +++ b/components/shared/compositing/tests/compositor.rs @@ -7,13 +7,12 @@ use std::cell::Cell; use base::id::ScrollTreeNodeId; use compositing_traits::display_list::{ AxesScrollSensitivity, ScrollTree, ScrollType, ScrollableNodeInfo, SpatialTreeNodeInfo, - StickyNodeInfo, }; -use euclid::{SideOffsets2D, Size2D}; +use euclid::Size2D; use webrender_api::units::LayoutVector2D; -use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, StickyOffsetBounds}; +use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation}; -fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId { +fn add_mock_scroll_node(tree: &mut ScrollTree) -> (ScrollTreeNodeId, ExternalScrollId) { let pipeline_id = PipelineId(0, 0); let num_nodes = tree.nodes.len(); let parent = if num_nodes > 0 { @@ -24,10 +23,11 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId { None }; - tree.add_scroll_tree_node( + let external_id = ExternalScrollId(num_nodes as u64, pipeline_id); + let scroll_node_id = tree.add_scroll_tree_node( parent.as_ref(), SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo { - external_id: ExternalScrollId(num_nodes as u64, pipeline_id), + external_id, content_rect: Size2D::new(200.0, 200.0).into(), clip_rect: Size2D::new(100.0, 100.0).into(), scroll_sensitivity: AxesScrollSensitivity { @@ -37,42 +37,42 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId { offset: LayoutVector2D::zero(), offset_changed: Cell::new(false), }), - ) + ); + (scroll_node_id, external_id) } #[test] fn test_scroll_tree_simple_scroll() { let mut scroll_tree = ScrollTree::default(); - let pipeline_id = PipelineId(0, 0); - let id = add_mock_scroll_node(&mut scroll_tree); + let (id, external_id) = add_mock_scroll_node(&mut scroll_tree); let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( - &id, + &external_id, ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)), ScrollType::Script, ) .unwrap(); let expected_offset = LayoutVector2D::new(20.0, 40.0); - assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(scrolled_id, external_id); assert_eq!(offset, expected_offset); assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset)); let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( - &id, + &external_id, ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)), ScrollType::Script, ) .unwrap(); let expected_offset = LayoutVector2D::new(0.0, 0.0); - assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(scrolled_id, external_id); assert_eq!(offset, expected_offset); assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset)); // Scroll offsets must be positive. let result = scroll_tree.scroll_node_or_ancestor( - &id, + &external_id, ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)), ScrollType::Script, ); @@ -88,26 +88,33 @@ fn test_scroll_tree_simple_scroll_chaining() { let mut scroll_tree = ScrollTree::default(); let pipeline_id = PipelineId(0, 0); - let parent_id = add_mock_scroll_node(&mut scroll_tree); + let (parent_id, parent_external_id) = add_mock_scroll_node(&mut scroll_tree); + + let unscrollable_external_id = ExternalScrollId(100 as u64, pipeline_id); let unscrollable_child_id = scroll_tree.add_scroll_tree_node( Some(&parent_id), - SpatialTreeNodeInfo::Sticky(StickyNodeInfo { - frame_rect: Size2D::new(100.0, 100.0).into(), - margins: SideOffsets2D::default(), - vertical_offset_bounds: StickyOffsetBounds::new(0.0, 0.0), - horizontal_offset_bounds: StickyOffsetBounds::new(0.0, 0.0), + SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo { + external_id: unscrollable_external_id, + content_rect: Size2D::new(100.0, 100.0).into(), + clip_rect: Size2D::new(100.0, 100.0).into(), + scroll_sensitivity: AxesScrollSensitivity { + x: ScrollType::Script | ScrollType::InputEvents, + y: ScrollType::Script | ScrollType::InputEvents, + }, + offset: LayoutVector2D::zero(), + offset_changed: Cell::new(false), }), ); let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( - &unscrollable_child_id, + &unscrollable_external_id, ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)), ScrollType::Script, ) .unwrap(); let expected_offset = LayoutVector2D::new(20.0, 40.0); - assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(scrolled_id, parent_external_id); assert_eq!(offset, expected_offset); assert_eq!( scroll_tree.get_node(&parent_id).offset(), @@ -116,35 +123,37 @@ fn test_scroll_tree_simple_scroll_chaining() { let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( - &unscrollable_child_id, + &unscrollable_external_id, ScrollLocation::Delta(LayoutVector2D::new(10.0, 15.0)), ScrollType::Script, ) .unwrap(); let expected_offset = LayoutVector2D::new(30.0, 55.0); - assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(scrolled_id, parent_external_id); assert_eq!(offset, expected_offset); assert_eq!( scroll_tree.get_node(&parent_id).offset(), Some(expected_offset) ); - assert_eq!(scroll_tree.get_node(&unscrollable_child_id).offset(), None); + assert_eq!( + scroll_tree.get_node(&unscrollable_child_id).offset(), + Some(LayoutVector2D::zero()) + ); } #[test] fn test_scroll_tree_chain_when_at_extent() { let mut scroll_tree = ScrollTree::default(); - let pipeline_id = PipelineId(0, 0); - let parent_id = add_mock_scroll_node(&mut scroll_tree); - let child_id = add_mock_scroll_node(&mut scroll_tree); + let (parent_id, parent_external_id) = add_mock_scroll_node(&mut scroll_tree); + let (child_id, child_external_id) = add_mock_scroll_node(&mut scroll_tree); let (scrolled_id, offset) = scroll_tree - .scroll_node_or_ancestor(&child_id, ScrollLocation::End, ScrollType::Script) + .scroll_node_or_ancestor(&child_external_id, ScrollLocation::End, ScrollType::Script) .unwrap(); let expected_offset = LayoutVector2D::new(0.0, 100.0); - assert_eq!(scrolled_id, ExternalScrollId(1, pipeline_id)); + assert_eq!(scrolled_id, child_external_id); assert_eq!(offset, expected_offset); assert_eq!( scroll_tree.get_node(&child_id).offset(), @@ -155,13 +164,13 @@ fn test_scroll_tree_chain_when_at_extent() { // of its scroll area in the y axis. let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( - &child_id, + &child_external_id, ScrollLocation::Delta(LayoutVector2D::new(0.0, 10.0)), ScrollType::Script, ) .unwrap(); let expected_offset = LayoutVector2D::new(0.0, 10.0); - assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(scrolled_id, parent_external_id); assert_eq!(offset, expected_offset); assert_eq!( scroll_tree.get_node(&parent_id).offset(), @@ -175,9 +184,8 @@ fn test_scroll_tree_chain_through_overflow_hidden() { // Create a tree with a scrollable leaf, but make its `scroll_sensitivity` // reflect `overflow: hidden` ie not responsive to non-script scroll events. - let pipeline_id = PipelineId(0, 0); - let parent_id = add_mock_scroll_node(&mut scroll_tree); - let overflow_hidden_id = add_mock_scroll_node(&mut scroll_tree); + let (parent_id, parent_external_id) = add_mock_scroll_node(&mut scroll_tree); + let (overflow_hidden_id, overflow_hidden_external_id) = add_mock_scroll_node(&mut scroll_tree); let node = scroll_tree.get_node_mut(&overflow_hidden_id); if let SpatialTreeNodeInfo::Scroll(ref mut scroll_node_info) = node.info { @@ -189,13 +197,13 @@ fn test_scroll_tree_chain_through_overflow_hidden() { let (scrolled_id, offset) = scroll_tree .scroll_node_or_ancestor( - &overflow_hidden_id, + &overflow_hidden_external_id, ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)), ScrollType::InputEvents, ) .unwrap(); let expected_offset = LayoutVector2D::new(20.0, 40.0); - assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(scrolled_id, parent_external_id); assert_eq!(offset, expected_offset); assert_eq!( scroll_tree.get_node(&parent_id).offset(), diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index 82c49176f63..2247bb70067 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -20,7 +20,7 @@ use std::hash::Hash; use std::path::PathBuf; use std::sync::Arc; -use base::id::{PipelineId, ScrollTreeNodeId, WebViewId}; +use base::id::{PipelineId, WebViewId}; use crossbeam_channel::Sender; use euclid::{Point2D, Scale, Size2D}; use http::{HeaderMap, Method, StatusCode}; @@ -38,6 +38,7 @@ use style::queries::values::PrefersColorScheme; use style_traits::CSSPixel; use url::Url; use uuid::Uuid; +use webrender_api::ExternalScrollId; use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, LayoutSize}; pub use crate::input_events::*; @@ -888,17 +889,11 @@ pub struct CompositorHitTestResult { /// containing block. pub point_relative_to_initial_containing_block: Point2D, - /// The hit test point relative to the item itself. - pub point_relative_to_item: Point2D, - - /// The node address of the hit test result. - pub node: UntrustedNodeAddress, - /// The cursor that should be used when hovering the item hit by the hit test. pub cursor: Option, - /// The scroll tree node associated with this hit test item. - pub scroll_tree_node: ScrollTreeNodeId, + /// The [`ExternalScrollId`] of the scroll tree node associated with this hit test item. + pub external_scroll_id: ExternalScrollId, } /// Whether the default action for a touch event was prevented by web content diff --git a/components/shared/layout/Cargo.toml b/components/shared/layout/Cargo.toml index c1982e56e75..ae5d005d103 100644 --- a/components/shared/layout/Cargo.toml +++ b/components/shared/layout/Cargo.toml @@ -39,5 +39,6 @@ selectors = { workspace = true } serde = { workspace = true } servo_arc = { workspace = true } servo_url = { path = "../../url" } +stylo_traits = { workspace = true } stylo = { workspace = true } webrender_api = { workspace = true } diff --git a/components/shared/layout/lib.rs b/components/shared/layout/lib.rs index f3efb640129..72bb1910241 100644 --- a/components/shared/layout/lib.rs +++ b/components/shared/layout/lib.rs @@ -25,7 +25,8 @@ use bitflags::bitflags; use compositing_traits::CrossProcessCompositorApi; use constellation_traits::LoadData; use embedder_traits::{Theme, UntrustedNodeAddress, ViewportDetails}; -use euclid::default::{Point2D, Rect}; +use euclid::Point2D; +use euclid::default::{Point2D as UntypedPoint2D, Rect}; use fnv::FnvHashMap; use fonts::{FontContext, SystemFontServiceProxy}; use fxhash::FxHashMap; @@ -54,6 +55,7 @@ use style::properties::PropertyId; use style::properties::style_structs::Font; use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot}; use style::stylesheets::Stylesheet; +use style_traits::CSSPixel; use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D}; use webrender_api::{ExternalScrollId, ImageKey}; @@ -288,7 +290,7 @@ pub trait Layout { animation_timeline_value: f64, ) -> Option>; fn query_scrolling_area(&self, node: Option) -> Rect; - fn query_text_indext(&self, node: OpaqueNode, point: Point2D) -> Option; + fn query_text_indext(&self, node: OpaqueNode, point: UntypedPoint2D) -> Option; fn query_elements_from_point( &self, point: LayoutPoint, @@ -608,9 +610,9 @@ pub struct ElementsFromPointResult { /// An [`OpaqueNode`] that contains a pointer to the node hit by /// this hit test result. pub node: OpaqueNode, - /// The [`LayoutPoint`] of the original query point relative to the + /// The [`Point2D`] of the original query point relative to the /// node fragment rectangle. - pub point_in_target: LayoutPoint, + pub point_in_target: Point2D, } bitflags! {