Position insertion point in input field with mouse

This commit is contained in:
Florian Merz 2017-01-11 09:04:03 +01:00
parent c784bc6944
commit b40db5b55d
9 changed files with 219 additions and 50 deletions

View file

@ -48,10 +48,74 @@ pub struct DisplayList {
} }
impl DisplayList { impl DisplayList {
// Returns the text index within a node for the point of interest.
pub fn text_index(&self,
node: OpaqueNode,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap)
-> Option<usize> {
let mut result = Vec::new();
let mut translated_point = client_point.clone();
let mut traversal = DisplayListTraversal::new(self);
self.text_index_contents(node,
&mut traversal,
&mut translated_point,
client_point,
scroll_offsets,
&mut result);
result.pop()
}
pub fn text_index_contents<'a>(&self,
node: OpaqueNode,
traversal: &mut DisplayListTraversal<'a>,
translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<usize>) {
while let Some(item) = traversal.next() {
match item {
&DisplayItem::PushStackingContext(ref stacking_context_item) => {
DisplayList::translate_point(&stacking_context_item.stacking_context,
translated_point,
client_point);
self.text_index_contents(node,
traversal,
translated_point,
client_point,
scroll_offsets,
result);
}
&DisplayItem::PushScrollRoot(ref item) => {
DisplayList::scroll_root(&item.scroll_root,
translated_point,
scroll_offsets);
self.text_index_contents(node,
traversal,
translated_point,
client_point,
scroll_offsets,
result);
},
&DisplayItem::PopStackingContext(_) => return,
&DisplayItem::Text(ref text) => {
let base = item.base();
if base.metadata.node == node {
let offset = *translated_point - text.baseline_origin;
let index = text.text_run.range_index_of_advance(&text.range, offset.x);
result.push(index);
}
},
_ => {},
}
}
}
// Return all nodes containing the point of interest, bottommost first, and // Return all nodes containing the point of interest, bottommost first, and
// respecting the `pointer-events` CSS property. // respecting the `pointer-events` CSS property.
pub fn hit_test(&self, pub fn hit_test(&self,
translated_point: &Point2D<Au>, translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>, client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap) scroll_offsets: &ScrollOffsetMap)
-> Vec<DisplayItemMetadata> { -> Vec<DisplayItemMetadata> {
@ -67,27 +131,31 @@ impl DisplayList {
pub fn hit_test_contents<'a>(&self, pub fn hit_test_contents<'a>(&self,
traversal: &mut DisplayListTraversal<'a>, traversal: &mut DisplayListTraversal<'a>,
translated_point: &Point2D<Au>, translated_point: &mut Point2D<Au>,
client_point: &Point2D<Au>, client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap, scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) { result: &mut Vec<DisplayItemMetadata>) {
while let Some(item) = traversal.next() { while let Some(item) = traversal.next() {
match item { match item {
&DisplayItem::PushStackingContext(ref stacking_context_item) => { &DisplayItem::PushStackingContext(ref stacking_context_item) => {
self.hit_test_stacking_context(traversal, DisplayList::translate_point(&stacking_context_item.stacking_context,
&stacking_context_item.stacking_context, translated_point,
translated_point, client_point);
client_point, self.hit_test_contents(traversal,
scroll_offsets, translated_point,
result); client_point,
scroll_offsets,
result);
} }
&DisplayItem::PushScrollRoot(ref item) => { &DisplayItem::PushScrollRoot(ref item) => {
self.hit_test_scroll_root(traversal, DisplayList::scroll_root(&item.scroll_root,
&item.scroll_root, translated_point,
*translated_point, scroll_offsets);
client_point, self.hit_test_contents(traversal,
scroll_offsets, translated_point,
result); client_point,
scroll_offsets,
result);
} }
&DisplayItem::PopStackingContext(_) | &DisplayItem::PopScrollRoot(_) => return, &DisplayItem::PopStackingContext(_) | &DisplayItem::PopScrollRoot(_) => return,
_ => { _ => {
@ -99,38 +167,15 @@ impl DisplayList {
} }
} }
fn hit_test_scroll_root<'a>(&self, #[inline]
traversal: &mut DisplayListTraversal<'a>, fn translate_point<'a>(stacking_context: &StackingContext,
scroll_root: &ScrollRoot, translated_point: &mut Point2D<Au>,
mut translated_point: Point2D<Au>, client_point: &Point2D<Au>) {
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) {
// Adjust the translated point to account for the scroll offset if
// necessary. This can only happen when WebRender is in use.
//
// We don't perform this adjustment on the root stacking context because
// the DOM-side code has already translated the point for us (e.g. in
// `Window::hit_test_query()`) by now.
if let Some(scroll_offset) = scroll_offsets.get(&scroll_root.id) {
translated_point.x -= Au::from_f32_px(scroll_offset.x);
translated_point.y -= Au::from_f32_px(scroll_offset.y);
}
self.hit_test_contents(traversal, &translated_point, client_point, scroll_offsets, result);
}
fn hit_test_stacking_context<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
stacking_context: &StackingContext,
translated_point: &Point2D<Au>,
client_point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) {
// Convert the parent translated point into stacking context local transform space if the // Convert the parent translated point into stacking context local transform space if the
// stacking context isn't fixed. If it's fixed, we need to use the client point anyway. // stacking context isn't fixed. If it's fixed, we need to use the client point anyway.
debug_assert!(stacking_context.context_type == StackingContextType::Real); debug_assert!(stacking_context.context_type == StackingContextType::Real);
let is_fixed = stacking_context.scroll_policy == ScrollPolicy::Fixed; let is_fixed = stacking_context.scroll_policy == ScrollPolicy::Fixed;
let translated_point = if is_fixed { *translated_point = if is_fixed {
*client_point *client_point
} else { } else {
let point = *translated_point - stacking_context.bounds.origin; let point = *translated_point - stacking_context.bounds.origin;
@ -139,8 +184,21 @@ impl DisplayList {
point.y.to_f32_px())); point.y.to_f32_px()));
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y)) Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
}; };
}
self.hit_test_contents(traversal, &translated_point, client_point, scroll_offsets, result); #[inline]
fn scroll_root<'a>(scroll_root: &ScrollRoot,
translated_point: &mut Point2D<Au>,
scroll_offsets: &ScrollOffsetMap) {
// Adjust the translated point to account for the scroll offset if necessary.
//
// We don't perform this adjustment on the root stacking context because
// the DOM-side code has already translated the point for us (e.g. in
// `Window::hit_test_query()`) by now.
if let Some(scroll_offset) = scroll_offsets.get(&scroll_root.id) {
translated_point.x -= Au::from_f32_px(scroll_offset.x);
translated_point.y -= Au::from_f32_px(scroll_offset.y);
}
} }
pub fn print(&self) { pub fn print(&self) {

View file

@ -546,6 +546,27 @@ impl<'a> GlyphStore {
} }
} }
// Scan the glyphs for a given range until we reach a given advance. Returns the index
// and advance of the glyph in the range at the given advance, if reached. Otherwise, returns the
// the number of glyphs and the advance for the given range.
#[inline]
pub fn range_index_of_advance(&self, range: &Range<ByteIndex>, advance: Au, extra_word_spacing: Au) -> (usize, Au) {
let mut index = 0;
let mut current_advance = Au(0);
for glyph in self.iter_glyphs_for_byte_range(range) {
if glyph.char_is_space() {
current_advance += glyph.advance() + extra_word_spacing
} else {
current_advance += glyph.advance()
}
if current_advance > advance {
break;
}
index += 1;
}
(index, current_advance)
}
#[inline] #[inline]
pub fn advance_for_byte_range(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au { pub fn advance_for_byte_range(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au {
if range.begin() == ByteIndex(0) && range.end() == self.len() { if range.begin() == ByteIndex(0) && range.end() == self.len() {

View file

@ -304,6 +304,21 @@ impl<'a> TextRun {
}) })
} }
/// Returns the index in the range of the first glyph advancing over given advance
pub fn range_index_of_advance(&self, range: &Range<ByteIndex>, advance: Au) -> usize {
// TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
let mut remaining = advance;
self.natural_word_slices_in_range(range)
.map(|slice| {
let (slice_index, slice_advance) =
slice.glyphs.range_index_of_advance(&slice.range, remaining, self.extra_word_spacing);
remaining -= slice_advance;
slice_index
})
.sum()
}
/// Returns an iterator that will iterate over all slices of glyphs that represent natural /// Returns an iterator that will iterate over all slices of glyphs that represent natural
/// words in the given range. /// words in the given range.
pub fn natural_word_slices_in_range(&'a self, range: &Range<ByteIndex>) pub fn natural_word_slices_in_range(&'a self, range: &Range<ByteIndex>)

View file

@ -20,7 +20,7 @@ use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse};
use script_layout_interface::rpc::{HitTestResponse, LayoutRPC}; use script_layout_interface::rpc::{HitTestResponse, LayoutRPC};
use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse}; use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse};
use script_layout_interface::rpc::{NodeOverflowResponse, OffsetParentResponse}; use script_layout_interface::rpc::{NodeOverflowResponse, OffsetParentResponse};
use script_layout_interface::rpc::{NodeScrollRootIdResponse, ResolvedStyleResponse}; use script_layout_interface::rpc::{NodeScrollRootIdResponse, ResolvedStyleResponse, TextIndexResponse};
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode}; use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use script_traits::LayoutMsg as ConstellationMsg; use script_traits::LayoutMsg as ConstellationMsg;
use script_traits::UntrustedNodeAddress; use script_traits::UntrustedNodeAddress;
@ -85,6 +85,9 @@ pub struct LayoutThreadData {
/// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use. /// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
pub stacking_context_scroll_offsets: ScrollOffsetMap, pub stacking_context_scroll_offsets: ScrollOffsetMap,
/// Index in a text fragment. We need this do determine the insertion point.
pub text_index_response: TextIndexResponse,
} }
pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>); pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
@ -138,7 +141,7 @@ impl LayoutRPC for LayoutRPCImpl {
fn nodes_from_point(&self, fn nodes_from_point(&self,
page_point: Point2D<f32>, page_point: Point2D<f32>,
client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress> { client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress> {
let page_point = Point2D::new(Au::from_f32_px(page_point.x), let mut page_point = Point2D::new(Au::from_f32_px(page_point.x),
Au::from_f32_px(page_point.y)); Au::from_f32_px(page_point.y));
let client_point = Point2D::new(Au::from_f32_px(client_point.x), let client_point = Point2D::new(Au::from_f32_px(client_point.x),
Au::from_f32_px(client_point.y)); Au::from_f32_px(client_point.y));
@ -149,7 +152,7 @@ impl LayoutRPC for LayoutRPCImpl {
let result = match rw_data.display_list { let result = match rw_data.display_list {
None => panic!("Tried to hit test without a DisplayList"), None => panic!("Tried to hit test without a DisplayList"),
Some(ref display_list) => { Some(ref display_list) => {
display_list.hit_test(&page_point, display_list.hit_test(&mut page_point,
&client_point, &client_point,
&rw_data.stacking_context_scroll_offsets) &rw_data.stacking_context_scroll_offsets)
} }
@ -206,6 +209,12 @@ impl LayoutRPC for LayoutRPCImpl {
let rw_data = rw_data.lock().unwrap(); let rw_data = rw_data.lock().unwrap();
rw_data.margin_style_response.clone() rw_data.margin_style_response.clone()
} }
fn text_index(&self) -> TextIndexResponse {
let &LayoutRPCImpl(ref rw_data) = self;
let rw_data = rw_data.lock().unwrap();
rw_data.text_index_response.clone()
}
} }
struct UnioningFragmentBorderBoxIterator { struct UnioningFragmentBorderBoxIterator {
@ -581,6 +590,7 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
} }
} }
pub fn process_node_geometry_request<N: LayoutNode>(requested_node: N, layout_root: &mut Flow) pub fn process_node_geometry_request<N: LayoutNode>(requested_node: N, layout_root: &mut Flow)
-> Rect<i32> { -> Rect<i32> {
let mut iterator = FragmentLocatingFragmentIterator::new(requested_node.opaque()); let mut iterator = FragmentLocatingFragmentIterator::new(requested_node.opaque());

View file

@ -91,6 +91,7 @@ use script::layout_wrapper::{ServoLayoutElement, ServoLayoutDocument, ServoLayou
use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow}; use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow};
use script_layout_interface::reporter::CSSErrorReporter; use script_layout_interface::reporter::CSSErrorReporter;
use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse}; use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse};
use script_layout_interface::rpc::TextIndexResponse;
use script_layout_interface::wrapper_traits::LayoutNode; use script_layout_interface::wrapper_traits::LayoutNode;
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg}; use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::{StackingContextScrollState, UntrustedNodeAddress}; use script_traits::{StackingContextScrollState, UntrustedNodeAddress};
@ -474,6 +475,7 @@ impl LayoutThread {
offset_parent_response: OffsetParentResponse::empty(), offset_parent_response: OffsetParentResponse::empty(),
margin_style_response: MarginStyleResponse::empty(), margin_style_response: MarginStyleResponse::empty(),
stacking_context_scroll_offsets: HashMap::new(), stacking_context_scroll_offsets: HashMap::new(),
text_index_response: TextIndexResponse(None),
})), })),
error_reporter: CSSErrorReporter { error_reporter: CSSErrorReporter {
pipelineid: id, pipelineid: id,
@ -1039,6 +1041,9 @@ impl LayoutThread {
ReflowQueryType::MarginStyleQuery(_) => { ReflowQueryType::MarginStyleQuery(_) => {
rw_data.margin_style_response = MarginStyleResponse::empty(); rw_data.margin_style_response = MarginStyleResponse::empty();
}, },
ReflowQueryType::TextIndexQuery(..) => {
rw_data.text_index_response = TextIndexResponse(None);
}
ReflowQueryType::NoQuery => {} ReflowQueryType::NoQuery => {}
} }
return; return;
@ -1243,7 +1248,7 @@ impl LayoutThread {
rw_data.content_boxes_response = process_content_boxes_request(node, root_flow); rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
}, },
ReflowQueryType::HitTestQuery(translated_point, client_point, update_cursor) => { ReflowQueryType::HitTestQuery(translated_point, client_point, update_cursor) => {
let translated_point = Point2D::new(Au::from_f32_px(translated_point.x), let mut translated_point = Point2D::new(Au::from_f32_px(translated_point.x),
Au::from_f32_px(translated_point.y)); Au::from_f32_px(translated_point.y));
let client_point = Point2D::new(Au::from_f32_px(client_point.x), let client_point = Point2D::new(Au::from_f32_px(client_point.x),
@ -1252,11 +1257,24 @@ impl LayoutThread {
let result = rw_data.display_list let result = rw_data.display_list
.as_ref() .as_ref()
.expect("Tried to hit test with no display list") .expect("Tried to hit test with no display list")
.hit_test(&translated_point, .hit_test(&mut translated_point,
&client_point, &client_point,
&rw_data.stacking_context_scroll_offsets); &rw_data.stacking_context_scroll_offsets);
rw_data.hit_test_response = (result.last().cloned(), update_cursor); rw_data.hit_test_response = (result.last().cloned(), update_cursor);
}, },
ReflowQueryType::TextIndexQuery(node, mouse_x, mouse_y) => {
let node = unsafe { ServoLayoutNode::new(&node) };
let opaque_node = node.opaque();
let client_point = Point2D::new(Au::from_px(mouse_x),
Au::from_px(mouse_y));
rw_data.text_index_response =
TextIndexResponse(rw_data.display_list
.as_ref()
.expect("Tried to hit test with no display list")
.text_index(opaque_node,
&client_point,
&rw_data.stacking_context_scroll_offsets));
},
ReflowQueryType::NodeGeometryQuery(node) => { ReflowQueryType::NodeGeometryQuery(node) => {
let node = unsafe { ServoLayoutNode::new(&node) }; let node = unsafe { ServoLayoutNode::new(&node) };
rw_data.client_rect_response = process_node_geometry_request(node, root_flow); rw_data.client_rect_response = process_node_geometry_request(node, root_flow);
@ -1593,7 +1611,7 @@ fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
/// or false if it only needs stacking-relative positions. /// or false if it only needs stacking-relative positions.
fn reflow_query_type_needs_display_list(query_type: &ReflowQueryType) -> bool { fn reflow_query_type_needs_display_list(query_type: &ReflowQueryType) -> bool {
match *query_type { match *query_type {
ReflowQueryType::HitTestQuery(..) => true, ReflowQueryType::HitTestQuery(..) | ReflowQueryType::TextIndexQuery(..) => true,
ReflowQueryType::ContentBoxQuery(_) | ReflowQueryType::ContentBoxesQuery(_) | ReflowQueryType::ContentBoxQuery(_) | ReflowQueryType::ContentBoxesQuery(_) |
ReflowQueryType::NodeGeometryQuery(_) | ReflowQueryType::NodeScrollGeometryQuery(_) | ReflowQueryType::NodeGeometryQuery(_) | ReflowQueryType::NodeScrollGeometryQuery(_) |
ReflowQueryType::NodeOverflowQuery(_) | ReflowQueryType::NodeScrollRootIdQuery(_) | ReflowQueryType::NodeOverflowQuery(_) | ReflowQueryType::NodeScrollRootIdQuery(_) |

View file

@ -11,6 +11,8 @@ use dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding; use dom::bindings::codegen::Bindings::HTMLInputElementBinding;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::error::{Error, ErrorResult}; use dom::bindings::error::{Error, ErrorResult};
use dom::bindings::inheritance::Castable; use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root, RootedReference}; use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root, RootedReference};
@ -27,6 +29,7 @@ use dom::htmlfieldsetelement::HTMLFieldSetElement;
use dom::htmlformelement::{FormControl, FormDatum, FormDatumValue, FormSubmitter, HTMLFormElement}; use dom::htmlformelement::{FormControl, FormDatum, FormDatumValue, FormSubmitter, HTMLFormElement};
use dom::htmlformelement::{ResetFrom, SubmittedFrom}; use dom::htmlformelement::{ResetFrom, SubmittedFrom};
use dom::keyboardevent::KeyboardEvent; use dom::keyboardevent::KeyboardEvent;
use dom::mouseevent::MouseEvent;
use dom::node::{Node, NodeDamage, UnbindContext}; use dom::node::{Node, NodeDamage, UnbindContext};
use dom::node::{document_from_node, window_from_node}; use dom::node::{document_from_node, window_from_node};
use dom::nodelist::NodeList; use dom::nodelist::NodeList;
@ -39,6 +42,7 @@ use mime_guess;
use net_traits::{CoreResourceMsg, IpcSend}; use net_traits::{CoreResourceMsg, IpcSend};
use net_traits::blob_url_store::get_blob_origin; use net_traits::blob_url_store::get_blob_origin;
use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern}; use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern};
use script_layout_interface::rpc::TextIndexResponse;
use script_traits::ScriptMsg as ConstellationMsg; use script_traits::ScriptMsg as ConstellationMsg;
use servo_atoms::Atom; use servo_atoms::Atom;
use std::borrow::ToOwned; use std::borrow::ToOwned;
@ -1088,6 +1092,33 @@ impl VirtualMethods for HTMLInputElement {
//TODO: set the editing position for text inputs //TODO: set the editing position for text inputs
document_from_node(self).request_focus(self.upcast()); document_from_node(self).request_focus(self.upcast());
if (self.input_type.get() == InputType::InputText ||
self.input_type.get() == InputType::InputPassword) &&
// Check if we display a placeholder. Layout doesn't know about this.
!self.textinput.borrow().is_empty() {
if let Some(mouse_event) = event.downcast::<MouseEvent>() {
// dispatch_key_event (document.rs) triggers a click event when releasing
// the space key. There's no nice way to catch this so let's use this for
// now.
if !(mouse_event.ScreenX() == 0 && mouse_event.ScreenY() == 0 &&
mouse_event.GetRelatedTarget().is_none()) {
let window = window_from_node(self);
let translated_x = mouse_event.ClientX() + window.PageXOffset();
let translated_y = mouse_event.ClientY() + window.PageYOffset();
let TextIndexResponse(index) = window.text_index_query(
self.upcast::<Node>().to_trusted_node_address(),
translated_x,
translated_y
);
if let Some(i) = index {
self.textinput.borrow_mut().edit_point.index = i as usize;
// trigger redraw
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
event.PreventDefault();
}
}
}
}
} else if event.type_() == atom!("keydown") && !event.DefaultPrevented() && } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() &&
(self.input_type.get() == InputType::InputText || (self.input_type.get() == InputType::InputText ||
self.input_type.get() == InputType::InputPassword) { self.input_type.get() == InputType::InputPassword) {

View file

@ -68,7 +68,7 @@ use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflo
use script_layout_interface::reporter::CSSErrorReporter; use script_layout_interface::reporter::CSSErrorReporter;
use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC}; use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse}; use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse};
use script_layout_interface::rpc::ResolvedStyleResponse; use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse};
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory};
use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper}; use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper};
use script_thread::SendableMainThreadScriptChan; use script_thread::SendableMainThreadScriptChan;
@ -1362,6 +1362,15 @@ impl Window {
self.layout_rpc.margin_style() self.layout_rpc.margin_style()
} }
pub fn text_index_query(&self, node: TrustedNodeAddress, mouse_x: i32, mouse_y: i32) -> TextIndexResponse {
if !self.reflow(ReflowGoal::ForScriptQuery,
ReflowQueryType::TextIndexQuery(node, mouse_x, mouse_y),
ReflowReason::Query) {
return TextIndexResponse(None);
}
self.layout_rpc.text_index()
}
#[allow(unsafe_code)] #[allow(unsafe_code)]
pub fn init_browsing_context(&self, browsing_context: &BrowsingContext) { pub fn init_browsing_context(&self, browsing_context: &BrowsingContext) {
assert!(self.browsing_context.get().is_none()); assert!(self.browsing_context.get().is_none());
@ -1710,6 +1719,7 @@ fn debug_reflow_events(id: PipelineId, goal: &ReflowGoal, query_type: &ReflowQue
ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery", ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery", ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery", ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery",
ReflowQueryType::TextIndexQuery(..) => "\tTextIndexQuery",
}); });
debug_msg.push_str(match *reason { debug_msg.push_str(match *reason {

View file

@ -100,6 +100,7 @@ pub enum ReflowQueryType {
ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId), ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId),
OffsetParentQuery(TrustedNodeAddress), OffsetParentQuery(TrustedNodeAddress),
MarginStyleQuery(TrustedNodeAddress), MarginStyleQuery(TrustedNodeAddress),
TextIndexQuery(TrustedNodeAddress, i32, i32),
} }
/// Information needed for a reflow. /// Information needed for a reflow.

View file

@ -39,6 +39,8 @@ pub trait LayoutRPC {
fn margin_style(&self) -> MarginStyleResponse; fn margin_style(&self) -> MarginStyleResponse;
fn nodes_from_point(&self, page_point: Point2D<f32>, client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress>; fn nodes_from_point(&self, page_point: Point2D<f32>, client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress>;
fn text_index(&self) -> TextIndexResponse;
} }
pub struct ContentBoxResponse(pub Rect<Au>); pub struct ContentBoxResponse(pub Rect<Au>);
@ -92,3 +94,6 @@ impl MarginStyleResponse {
} }
} }
} }
#[derive(Clone)]
pub struct TextIndexResponse(pub Option<usize>);