Auto merge of #29490 - mrobinson:move-hit-testing-out, r=mukilan

Move hit testing information out of WebRender

Store hit testing information in a data structure that sits alongside the display list in the compositor. This will allow the compositor to store more information per-node. The data structure also takes care of de-duplicating information between successive display list entries. In the future, the data structure can be even more aggressive in producing smaller side hit testing lists, if necessary.

<!-- Please describe your changes on the following line: -->

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes do not require tests because they should not change behavior.

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2023-03-16 12:41:37 +01:00 committed by GitHub
commit a8da28e55d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 225 additions and 107 deletions

View file

@ -28,16 +28,16 @@ use msg::constellation_msg::{
}; };
use net_traits::image::base::Image; use net_traits::image::base::Image;
use net_traits::image_cache::CorsStatus; use net_traits::image_cache::CorsStatus;
use num_traits::FromPrimitive;
#[cfg(feature = "gl")] #[cfg(feature = "gl")]
use pixels::PixelFormat; use pixels::PixelFormat;
use profile_traits::time::{self as profile_time, profile, ProfilerCategory}; use profile_traits::time::{self as profile_time, profile, ProfilerCategory};
use script_traits::compositor::HitTestInfo;
use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent, TouchEvent, WheelEvent}; use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent, TouchEvent, WheelEvent};
use script_traits::{AnimationState, AnimationTickType, LayoutControlMsg};
use script_traits::{ use script_traits::{
MouseButton, MouseEventType, ScrollState, TouchEventType, TouchId, WheelDelta, AnimationState, AnimationTickType, CompositorHitTestResult, LayoutControlMsg, MouseButton,
MouseEventType, ScrollState, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta,
WindowSizeData, WindowSizeType,
}; };
use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType};
use servo_geometry::{DeviceIndependentPixel, FramebufferUintLength}; use servo_geometry::{DeviceIndependentPixel, FramebufferUintLength};
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
@ -48,8 +48,10 @@ use std::rc::Rc;
use style_traits::viewport::ViewportConstraints; use style_traits::viewport::ViewportConstraints;
use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor}; use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor};
use time::{now, precise_time_ns, precise_time_s}; use time::{now, precise_time_ns, precise_time_s};
use webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePoint, LayoutVector2D}; use webrender_api::units::{
use webrender_api::{self, HitTestFlags, HitTestResult, ScrollLocation}; DeviceIntPoint, DeviceIntSize, DevicePoint, LayoutVector2D, WorldPoint,
};
use webrender_api::{self, HitTestFlags, ScrollLocation};
use webrender_surfman::WebrenderSurfman; use webrender_surfman::WebrenderSurfman;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -263,6 +265,10 @@ struct PipelineDetails {
/// Whether this pipeline is visible /// Whether this pipeline is visible
visible: bool, visible: bool,
/// Hit test items for this pipeline. This is used to map WebRender hit test
/// information to the full information necessary for Servo.
hit_test_items: Vec<HitTestInfo>,
} }
impl PipelineDetails { impl PipelineDetails {
@ -272,6 +278,7 @@ impl PipelineDetails {
animations_running: false, animations_running: false,
animation_callbacks_running: false, animation_callbacks_running: false,
visible: true, visible: true,
hit_test_items: Vec::new(),
} }
} }
} }
@ -381,17 +388,16 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
self.webrender.deinit(); self.webrender.deinit();
} }
pub fn update_cursor(&mut self, hit_test_results: HitTestResult) { fn update_cursor(&mut self, result: CompositorHitTestResult) {
if let Some(item) = hit_test_results.items.first() { let cursor = match result.cursor {
if let Some(cursor) = Cursor::from_u8(item.tag.1 as _) { Some(cursor) if cursor != self.cursor => cursor,
if cursor != self.cursor { _ => return,
self.cursor = cursor; };
let msg = ConstellationMsg::SetCursor(cursor);
if let Err(e) = self.constellation_chan.send(msg) { self.cursor = cursor;
warn!("Sending event to constellation failed ({:?}).", e); let msg = ConstellationMsg::SetCursor(cursor);
} if let Err(e) = self.constellation_chan.send(msg) {
} warn!("Sending event to constellation failed ({:?}).", e);
}
} }
} }
@ -514,7 +520,9 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
(Msg::NewScrollFrameReady(recomposite_needed), ShutdownState::NotShuttingDown) => { (Msg::NewScrollFrameReady(recomposite_needed), ShutdownState::NotShuttingDown) => {
self.waiting_for_results_of_scroll = false; self.waiting_for_results_of_scroll = false;
self.update_cursor(self.hit_test_at_point(self.cursor_pos)); if let Some(result) = self.hit_test_at_device_point(self.cursor_pos) {
self.update_cursor(result);
}
if recomposite_needed { if recomposite_needed {
self.composition_request = CompositionRequest::CompositeNow( self.composition_request = CompositionRequest::CompositeNow(
CompositingReason::NewWebRenderScrollFrame, CompositingReason::NewWebRenderScrollFrame,
@ -628,9 +636,14 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
size2, size2,
receiver, receiver,
descriptor, descriptor,
compositor_display_list_info,
)) => match receiver.recv() { )) => match receiver.recv() {
Ok(data) => { Ok(data) => {
self.waiting_on_pending_frame = true; self.waiting_on_pending_frame = true;
let details = self.pipeline_details(PipelineId::from_webrender(pipeline));
details.hit_test_items = compositor_display_list_info.hit_test_info;
let mut txn = webrender_api::Transaction::new(); let mut txn = webrender_api::Transaction::new();
txn.set_display_list( txn.set_display_list(
epoch, epoch,
@ -656,9 +669,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
flags, flags,
sender, sender,
)) => { )) => {
let result = let result = self.hit_test_at_point_with_flags_and_pipeline(point, flags, pipeline);
self.webrender_api
.hit_test(self.webrender_document, pipeline, point, flags);
let _ = sender.send(result); let _ = sender.send(result);
}, },
@ -904,8 +915,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
MouseWindowEvent::MouseUp(_, p) => p, MouseWindowEvent::MouseUp(_, p) => p,
}; };
let results = self.hit_test_at_point(point); let result = match self.hit_test_at_device_point(point) {
let result = match results.items.first() {
Some(result) => result, Some(result) => result,
None => return, None => return,
}; };
@ -920,29 +930,68 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
event_type, event_type,
button, button,
result.point_in_viewport.to_untyped(), result.point_in_viewport.to_untyped(),
Some(UntrustedNodeAddress(result.tag.0 as *const c_void)), Some(result.node),
Some(result.point_relative_to_item.to_untyped()), Some(result.point_relative_to_item),
button as u16, button as u16,
); );
let pipeline_id = PipelineId::from_webrender(result.pipeline); let msg = ConstellationMsg::ForwardEvent(result.pipeline_id, event_to_send);
let msg = ConstellationMsg::ForwardEvent(pipeline_id, event_to_send);
if let Err(e) = self.constellation_chan.send(msg) { if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending event to constellation failed ({:?}).", e); warn!("Sending event to constellation failed ({:?}).", e);
} }
} }
fn hit_test_at_point(&self, point: DevicePoint) -> HitTestResult { fn hit_test_at_device_point(&self, point: DevicePoint) -> Option<CompositorHitTestResult> {
let dppx = self.page_zoom * self.hidpi_factor(); let dppx = self.page_zoom * self.hidpi_factor();
let scaled_point = (point / dppx).to_untyped(); let scaled_point = (point / dppx).to_untyped();
let world_point = WorldPoint::from_untyped(scaled_point);
return self.hit_test_at_point(world_point);
}
let world_cursor = webrender_api::units::WorldPoint::from_untyped(scaled_point); fn hit_test_at_point(&self, point: WorldPoint) -> Option<CompositorHitTestResult> {
self.webrender_api.hit_test( return self
self.webrender_document, .hit_test_at_point_with_flags_and_pipeline(point, HitTestFlags::empty(), None)
None, .first()
world_cursor, .cloned();
HitTestFlags::empty(), }
)
fn hit_test_at_point_with_flags_and_pipeline(
&self,
point: WorldPoint,
flags: HitTestFlags,
pipeline_id: Option<webrender_api::PipelineId>,
) -> Vec<CompositorHitTestResult> {
let root_pipeline_id = match self.root_pipeline.id {
Some(root_pipeline_id) => root_pipeline_id,
None => return vec![],
};
if self.pipeline(root_pipeline_id).is_none() {
return vec![];
}
let results =
self.webrender_api
.hit_test(self.webrender_document, pipeline_id, point, flags);
results
.items
.iter()
.filter_map(|item| {
let pipeline_id = PipelineId::from_webrender(item.pipeline);
let details = match self.pipeline_details.get(&pipeline_id) {
Some(details) => details,
None => return None,
};
let info = &details.hit_test_items[item.tag.0 as usize];
Some(CompositorHitTestResult {
pipeline_id,
point_in_viewport: item.point_in_viewport.to_untyped(),
point_relative_to_item: item.point_relative_to_item.to_untyped(),
node: UntrustedNodeAddress(info.node as *const c_void),
cursor: info.cursor,
})
})
.collect()
} }
pub fn on_mouse_window_move_event_class(&mut self, cursor: DevicePoint) { pub fn on_mouse_window_move_event_class(&mut self, cursor: DevicePoint) {
@ -955,25 +1004,17 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
} }
fn dispatch_mouse_window_move_event_class(&mut self, cursor: DevicePoint) { fn dispatch_mouse_window_move_event_class(&mut self, cursor: DevicePoint) {
let root_pipeline_id = match self.root_pipeline.id { let result = match self.hit_test_at_device_point(cursor) {
Some(root_pipeline_id) => root_pipeline_id, Some(result) => result,
None => return, None => return,
}; };
if self.pipeline(root_pipeline_id).is_none() {
return;
}
let results = self.hit_test_at_point(cursor); let event = MouseMoveEvent(result.point_in_viewport, Some(result.node), 0);
if let Some(item) = results.items.first() { let msg = ConstellationMsg::ForwardEvent(result.pipeline_id, event);
let node_address = Some(UntrustedNodeAddress(item.tag.0 as *const c_void)); if let Err(e) = self.constellation_chan.send(msg) {
let event = MouseMoveEvent(item.point_in_viewport.to_untyped(), node_address, 0); warn!("Sending event to constellation failed ({:?}).", e);
let pipeline_id = PipelineId::from_webrender(item.pipeline);
let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending event to constellation failed ({:?}).", e);
}
self.update_cursor(results);
} }
self.update_cursor(result);
} }
fn send_touch_event( fn send_touch_event(
@ -982,16 +1023,14 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
identifier: TouchId, identifier: TouchId,
point: DevicePoint, point: DevicePoint,
) { ) {
let results = self.hit_test_at_point(point); if let Some(result) = self.hit_test_at_device_point(point) {
if let Some(item) = results.items.first() {
let event = TouchEvent( let event = TouchEvent(
event_type, event_type,
identifier, identifier,
item.point_in_viewport.to_untyped(), result.point_in_viewport,
Some(UntrustedNodeAddress(item.tag.0 as *const c_void)), Some(result.node),
); );
let pipeline_id = PipelineId::from_webrender(item.pipeline); let msg = ConstellationMsg::ForwardEvent(result.pipeline_id, event);
let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
if let Err(e) = self.constellation_chan.send(msg) { if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending event to constellation failed ({:?}).", e); warn!("Sending event to constellation failed ({:?}).", e);
} }
@ -999,15 +1038,9 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
} }
pub fn send_wheel_event(&mut self, delta: WheelDelta, point: DevicePoint) { pub fn send_wheel_event(&mut self, delta: WheelDelta, point: DevicePoint) {
let results = self.hit_test_at_point(point); if let Some(result) = self.hit_test_at_device_point(point) {
if let Some(item) = results.items.first() { let event = WheelEvent(delta, result.point_in_viewport, Some(result.node));
let event = WheelEvent( let msg = ConstellationMsg::ForwardEvent(result.pipeline_id, event);
delta,
item.point_in_viewport.to_untyped(),
Some(UntrustedNodeAddress(item.tag.0 as *const c_void)),
);
let pipeline_id = PipelineId::from_webrender(item.pipeline);
let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
if let Err(e) = self.constellation_chan.send(msg) { if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending event to constellation failed ({:?}).", e); warn!("Sending event to constellation failed ({:?}).", e);
} }
@ -1169,7 +1202,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
sl @ ScrollLocation::Start | sl @ ScrollLocation::End => sl, sl @ ScrollLocation::Start | sl @ ScrollLocation::End => sl,
}; };
let cursor = (combined_event.cursor.to_f32() / self.scale).to_untyped(); let cursor = (combined_event.cursor.to_f32() / self.scale).to_untyped();
let cursor = webrender_api::units::WorldPoint::from_untyped(cursor); let cursor = WorldPoint::from_untyped(cursor);
let mut txn = webrender_api::Transaction::new(); let mut txn = webrender_api::Transaction::new();
txn.scroll(scroll_location, cursor); txn.scroll(scroll_location, cursor);
if combined_event.magnification != 1.0 { if combined_event.magnification != 1.0 {

View file

@ -26,7 +26,7 @@ pub use webxr_api::MainThreadWaker as EventLoopWaker;
/// A cursor for the window. This is different from a CSS cursor (see /// A cursor for the window. This is different from a CSS cursor (see
/// `CursorKind`) in that it has no `Auto` value. /// `CursorKind`) in that it has no `Auto` value.
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Deserialize, Eq, FromPrimitive, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, Eq, FromPrimitive, PartialEq, Serialize)]
pub enum Cursor { pub enum Cursor {
None, None,
Default, Default,

View file

@ -413,11 +413,7 @@ impl<'a> DisplayListBuildState<'a> {
clipping_and_scrolling: ClippingAndScrolling, clipping_and_scrolling: ClippingAndScrolling,
) -> BaseDisplayItem { ) -> BaseDisplayItem {
BaseDisplayItem::new( BaseDisplayItem::new(
DisplayItemMetadata { DisplayItemMetadata { node, cursor },
node,
// Store cursor id in display list.
pointing: cursor.map(|x| x as u16),
},
clip_rect.to_layout(), clip_rect.to_layout(),
section, section,
self.current_stacking_context_id, self.current_stacking_context_id,

View file

@ -12,6 +12,7 @@
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of //! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
//! low-level drawing primitives. //! low-level drawing primitives.
use embedder_traits::Cursor;
use euclid::{SideOffsets2D, Vector2D}; use euclid::{SideOffsets2D, Vector2D};
use gfx_traits::print_tree::PrintTree; use gfx_traits::print_tree::PrintTree;
use gfx_traits::{self, StackingContextId}; use gfx_traits::{self, StackingContextId};
@ -465,7 +466,7 @@ impl BaseDisplayItem {
BaseDisplayItem { BaseDisplayItem {
metadata: DisplayItemMetadata { metadata: DisplayItemMetadata {
node: OpaqueNode(0), node: OpaqueNode(0),
pointing: None, cursor: None,
}, },
// Create a rectangle of maximal size. // Create a rectangle of maximal size.
clip_rect: LayoutRect::max_rect(), clip_rect: LayoutRect::max_rect(),
@ -542,7 +543,7 @@ pub struct DisplayItemMetadata {
pub node: OpaqueNode, pub node: OpaqueNode,
/// The value of the `cursor` property when the mouse hovers over this display item. If `None`, /// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
/// this display item is ineligible for pointer events (`pointer-events: none`). /// this display item is ineligible for pointer events (`pointer-events: none`).
pub pointing: Option<u16>, pub cursor: Option<Cursor>,
} }
#[derive(Clone, Eq, PartialEq, Serialize)] #[derive(Clone, Eq, PartialEq, Serialize)]

View file

@ -10,6 +10,7 @@
use crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType, ClipType}; use crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType, ClipType};
use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType}; use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType};
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use script_traits::compositor::CompositorDisplayListInfo;
use webrender_api::units::{LayoutPoint, LayoutVector2D}; use webrender_api::units::{LayoutPoint, LayoutVector2D};
use webrender_api::{ use webrender_api::{
self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem, DisplayListBuilder, self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem, DisplayListBuilder,
@ -22,6 +23,7 @@ struct ClipScrollState {
spatial_ids: Vec<Option<SpatialId>>, spatial_ids: Vec<Option<SpatialId>>,
active_clip_id: ClipId, active_clip_id: ClipId,
active_spatial_id: SpatialId, active_spatial_id: SpatialId,
compositor_info: CompositorDisplayListInfo,
} }
impl ClipScrollState { impl ClipScrollState {
@ -34,6 +36,7 @@ impl ClipScrollState {
spatial_ids: vec![None; size], spatial_ids: vec![None; size],
active_clip_id: root_clip_id, active_clip_id: root_clip_id,
active_spatial_id: root_scroll_node_id, active_spatial_id: root_scroll_node_id,
compositor_info: CompositorDisplayListInfo::default(),
}; };
// We need to register the WebRender root reference frame and root scroll node ids // We need to register the WebRender root reference frame and root scroll node ids
@ -82,7 +85,7 @@ impl DisplayList {
pub fn convert_to_webrender( pub fn convert_to_webrender(
&mut self, &mut self,
pipeline_id: PipelineId, pipeline_id: PipelineId,
) -> (DisplayListBuilder, IsContentful) { ) -> (DisplayListBuilder, CompositorDisplayListInfo, IsContentful) {
let webrender_pipeline = pipeline_id.to_webrender(); let webrender_pipeline = pipeline_id.to_webrender();
let mut state = ClipScrollState::new(self.clip_scroll_nodes.len(), webrender_pipeline); let mut state = ClipScrollState::new(self.clip_scroll_nodes.len(), webrender_pipeline);
@ -99,7 +102,7 @@ impl DisplayList {
.0; .0;
} }
(builder, is_contentful) (builder, state.compositor_info, is_contentful)
} }
} }
@ -132,9 +135,14 @@ impl DisplayItem {
state.active_clip_id = cur_clip_id; state.active_clip_id = cur_clip_id;
} }
let build_common_item_properties = |base: &BaseDisplayItem| { let mut build_common_item_properties = |base: &BaseDisplayItem| {
let tag = match base.metadata.pointing { let tag = match base.metadata.cursor {
Some(cursor) => Some((base.metadata.node.0 as u64, cursor)), Some(cursor) => {
let hit_test_index = state
.compositor_info
.add_hit_test_info(base.metadata.node.0 as u64, Some(cursor));
Some((hit_test_index as u64, 0u16))
},
None => None, None => None,
}; };
CommonItemProperties { CommonItemProperties {

View file

@ -13,6 +13,7 @@ use euclid::{Point2D, SideOffsets2D, Size2D};
use gfx::text::glyph::GlyphStore; use gfx::text::glyph::GlyphStore;
use mitochondria::OnceCell; use mitochondria::OnceCell;
use net_traits::image_cache::UsePlaceholder; use net_traits::image_cache::UsePlaceholder;
use script_traits::compositor::CompositorDisplayListInfo;
use std::sync::Arc; use std::sync::Arc;
use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle; use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
use style::dom::OpaqueNode; use style::dom::OpaqueNode;
@ -46,6 +47,7 @@ pub struct DisplayListBuilder<'a> {
element_for_canvas_background: OpaqueNode, element_for_canvas_background: OpaqueNode,
pub context: &'a LayoutContext<'a>, pub context: &'a LayoutContext<'a>,
pub wr: wr::DisplayListBuilder, pub wr: wr::DisplayListBuilder,
pub compositor_info: CompositorDisplayListInfo,
/// Contentful paint, for the purpose of /// Contentful paint, for the purpose of
/// https://w3c.github.io/paint-timing/#first-contentful-paint /// https://w3c.github.io/paint-timing/#first-contentful-paint
@ -66,6 +68,7 @@ impl<'a> DisplayListBuilder<'a> {
is_contentful: false, is_contentful: false,
context, context,
wr: wr::DisplayListBuilder::new(pipeline_id, fragment_tree.scrollable_overflow()), wr: wr::DisplayListBuilder::new(pipeline_id, fragment_tree.scrollable_overflow()),
compositor_info: CompositorDisplayListInfo::default(),
} }
} }
@ -85,6 +88,21 @@ impl<'a> DisplayListBuilder<'a> {
flags: style.get_webrender_primitive_flags(), flags: style.get_webrender_primitive_flags(),
} }
} }
fn hit_info(&mut self, style: &ComputedValues, tag: Tag, 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 {
None
} else {
let hit_test_index = self.compositor_info.add_hit_test_info(
tag.node().0 as u64,
Some(cursor(inherited_ui.cursor.keyword, auto_cursor)),
);
Some((hit_test_index as u64, 0u16))
}
}
} }
impl Fragment { impl Fragment {
@ -157,7 +175,7 @@ impl Fragment {
} }
let mut common = builder.common_properties(rect.to_webrender(), &fragment.parent_style); let mut common = builder.common_properties(rect.to_webrender(), &fragment.parent_style);
common.hit_info = hit_info(&fragment.parent_style, fragment.tag, Cursor::Text); common.hit_info = builder.hit_info(&fragment.parent_style, fragment.tag, Cursor::Text);
let color = fragment.parent_style.clone_color(); let color = fragment.parent_style.clone_color();
let font_metrics = &fragment.font_metrics; let font_metrics = &fragment.font_metrics;
@ -351,7 +369,7 @@ impl<'a> BuilderForBoxFragment<'a> {
} }
fn build_hit_test(&self, builder: &mut DisplayListBuilder) { fn build_hit_test(&self, builder: &mut DisplayListBuilder) {
let hit_info = hit_info(&self.fragment.style, self.fragment.tag, Cursor::Default); let hit_info = builder.hit_info(&self.fragment.style, self.fragment.tag, Cursor::Default);
if hit_info.is_some() { if hit_info.is_some() {
let mut common = builder.common_properties(self.border_rect, &self.fragment.style); let mut common = builder.common_properties(self.border_rect, &self.fragment.style);
common.hit_info = hit_info; common.hit_info = hit_info;
@ -559,18 +577,6 @@ fn glyphs(
glyphs glyphs
} }
fn hit_info(style: &ComputedValues, tag: Tag, 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 {
None
} else {
let cursor = cursor(inherited_ui.cursor.keyword, auto_cursor);
Some((tag.node().0 as u64, cursor as u16))
}
}
fn cursor(kind: CursorKind, auto_cursor: Cursor) -> Cursor { fn cursor(kind: CursorKind, auto_cursor: Cursor) -> Cursor {
match kind { match kind {
CursorKind::Auto => auto_cursor, CursorKind::Auto => auto_cursor,

View file

@ -1146,7 +1146,8 @@ impl LayoutThread {
debug!("Layout done!"); debug!("Layout done!");
// TODO: Avoid the temporary conversion and build webrender sc/dl directly! // TODO: Avoid the temporary conversion and build webrender sc/dl directly!
let (builder, is_contentful) = display_list.convert_to_webrender(self.id); let (builder, compositor_info, is_contentful) =
display_list.convert_to_webrender(self.id);
let viewport_size = Size2D::new( let viewport_size = Size2D::new(
self.viewport_size.width.to_f32_px(), self.viewport_size.width.to_f32_px(),
@ -1165,8 +1166,12 @@ impl LayoutThread {
self.paint_time_metrics self.paint_time_metrics
.maybe_observe_paint_time(self, epoch, is_contentful.0); .maybe_observe_paint_time(self, epoch, is_contentful.0);
self.webrender_api self.webrender_api.send_display_list(
.send_display_list(epoch, viewport_size, builder.finalize()); epoch,
viewport_size,
compositor_info,
builder.finalize(),
);
}, },
); );
} }
@ -1594,11 +1599,8 @@ impl LayoutThread {
flags, flags,
); );
rw_data.nodes_from_point_response = results rw_data.nodes_from_point_response =
.items results.iter().map(|result| result.node).collect()
.iter()
.map(|item| UntrustedNodeAddress(item.tag.0 as *const c_void))
.collect()
}, },
&QueryMsg::ElementInnerTextQuery(node) => { &QueryMsg::ElementInnerTextQuery(node) => {
let node = unsafe { ServoLayoutNode::new(&node) }; let node = unsafe { ServoLayoutNode::new(&node) };

View file

@ -1345,8 +1345,12 @@ impl LayoutThread {
self.viewport_size.width.to_f32_px(), self.viewport_size.width.to_f32_px(),
self.viewport_size.height.to_f32_px(), self.viewport_size.height.to_f32_px(),
)); ));
self.webrender_api self.webrender_api.send_display_list(
.send_display_list(epoch, viewport_size, display_list.wr.finalize()); epoch,
viewport_size,
display_list.compositor_info,
display_list.wr.finalize(),
);
if self.trace_layout { if self.trace_layout {
layout_debug::end_trace(self.generation.get()); layout_debug::end_trace(self.generation.get());

View file

@ -0,0 +1,44 @@
/* 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/. */
//! Defines data structures which are consumed by the Compositor.
use embedder_traits::Cursor;
/// Information that Servo keeps alongside WebRender display items
/// in order to add more context to hit test results.
#[derive(Debug, Deserialize, 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<Cursor>,
}
/// A data structure which stores compositor-side information about
/// display lists sent to the compositor.
/// by a WebRender display list.
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct CompositorDisplayListInfo {
/// 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<HitTestInfo>,
}
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<Cursor>) -> usize {
if let Some(last) = self.hit_test_info.last() {
if node == last.node && cursor == last.cursor {
return self.hit_test_info.len() - 1;
}
}
self.hit_test_info.push(HitTestInfo { node, cursor });
self.hit_test_info.len() - 1
}
}

View file

@ -18,11 +18,13 @@ extern crate malloc_size_of_derive;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
pub mod compositor;
mod script_msg; mod script_msg;
pub mod serializable; pub mod serializable;
pub mod transferable; pub mod transferable;
pub mod webdriver_msg; pub mod webdriver_msg;
use crate::compositor::CompositorDisplayListInfo;
use crate::serializable::{BlobData, BlobImpl}; use crate::serializable::{BlobData, BlobImpl};
use crate::transferable::MessagePortImpl; use crate::transferable::MessagePortImpl;
use crate::webdriver_msg::{LoadStatus, WebDriverScriptCommand}; use crate::webdriver_msg::{LoadStatus, WebDriverScriptCommand};
@ -30,7 +32,7 @@ use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLPipeline; use canvas_traits::webgl::WebGLPipeline;
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender}; use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId};
use embedder_traits::EventLoopWaker; use embedder_traits::{Cursor, EventLoopWaker};
use euclid::{default::Point2D, Length, Rect, Scale, Size2D, UnknownUnit, Vector2D}; use euclid::{default::Point2D, Length, Rect, Scale, Size2D, UnknownUnit, Vector2D};
use gfx_traits::Epoch; use gfx_traits::Epoch;
use http::HeaderMap; use http::HeaderMap;
@ -74,7 +76,7 @@ use webrender_api::{
BuiltDisplayList, DocumentId, ExternalImageData, ExternalScrollId, ImageData, ImageDescriptor, BuiltDisplayList, DocumentId, ExternalImageData, ExternalScrollId, ImageData, ImageDescriptor,
ImageKey, ScrollClamping, ImageKey, ScrollClamping,
}; };
use webrender_api::{BuiltDisplayListDescriptor, HitTestFlags, HitTestResult}; use webrender_api::{BuiltDisplayListDescriptor, HitTestFlags};
pub use crate::script_msg::{ pub use crate::script_msg::{
DOMMessage, HistoryEntryReplacement, Job, JobError, JobResult, JobResultValue, JobType, DOMMessage, HistoryEntryReplacement, Job, JobError, JobResult, JobResultValue, JobType,
@ -1110,6 +1112,25 @@ impl From<i32> for MediaSessionActionType {
} }
} }
/// The result of a hit test in the compositor.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CompositorHitTestResult {
/// The pipeline id of the resulting item.
pub pipeline_id: PipelineId,
/// The hit test point in the item's viewport.
pub point_in_viewport: euclid::default::Point2D<f32>,
/// The hit test point relative to the item itself.
pub point_relative_to_item: euclid::default::Point2D<f32>,
/// 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<Cursor>,
}
/// The set of WebRender operations that can be initiated by the content process. /// The set of WebRender operations that can be initiated by the content process.
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub enum WebrenderMsg { pub enum WebrenderMsg {
@ -1125,6 +1146,7 @@ pub enum WebrenderMsg {
LayoutSize, LayoutSize,
ipc::IpcBytesReceiver, ipc::IpcBytesReceiver,
BuiltDisplayListDescriptor, BuiltDisplayListDescriptor,
CompositorDisplayListInfo,
), ),
/// Perform a hit test operation. The result will be returned via /// Perform a hit test operation. The result will be returned via
/// the provided channel sender. /// the provided channel sender.
@ -1132,7 +1154,7 @@ pub enum WebrenderMsg {
Option<webrender_api::PipelineId>, Option<webrender_api::PipelineId>,
WorldPoint, WorldPoint,
HitTestFlags, HitTestFlags,
IpcSender<HitTestResult>, IpcSender<Vec<CompositorHitTestResult>>,
), ),
/// Create a new image key. The result will be returned via the /// Create a new image key. The result will be returned via the
/// provided channel sender. /// provided channel sender.
@ -1178,6 +1200,7 @@ impl WebrenderIpcSender {
&self, &self,
epoch: Epoch, epoch: Epoch,
size: LayoutSize, size: LayoutSize,
display_list_info: CompositorDisplayListInfo,
(pipeline, size2, list): (webrender_api::PipelineId, LayoutSize, BuiltDisplayList), (pipeline, size2, list): (webrender_api::PipelineId, LayoutSize, BuiltDisplayList),
) { ) {
let (data, descriptor) = list.into_data(); let (data, descriptor) = list.into_data();
@ -1189,6 +1212,7 @@ impl WebrenderIpcSender {
size2, size2,
receiver, receiver,
descriptor, descriptor,
display_list_info,
)) { )) {
warn!("Error sending display list: {}", e); warn!("Error sending display list: {}", e);
} }
@ -1205,7 +1229,7 @@ impl WebrenderIpcSender {
pipeline: Option<webrender_api::PipelineId>, pipeline: Option<webrender_api::PipelineId>,
point: WorldPoint, point: WorldPoint,
flags: HitTestFlags, flags: HitTestFlags,
) -> HitTestResult { ) -> Vec<CompositorHitTestResult> {
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
self.0 self.0
.send(WebrenderMsg::HitTest(pipeline, point, flags, sender)) .send(WebrenderMsg::HitTest(pipeline, point, flags, sender))