diff --git a/components/layout/construct.rs b/components/layout/construct.rs index dd1d58a6acc..a0b85802a66 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -41,7 +41,7 @@ use table_rowgroup::TableRowGroupFlow; use table_wrapper::TableWrapperFlow; use text::TextRunScanner; use traversal::PostorderNodeMutTraversal; -use wrapper::{PseudoElementType, ThreadSafeLayoutNode}; +use wrapper::{PseudoElementType, TextContent, ThreadSafeLayoutNode}; use gfx::display_list::OpaqueNode; use script::dom::characterdata::CharacterDataTypeId; @@ -583,7 +583,7 @@ impl<'a> FlowConstructor<'a> { // Add whitespace results. They will be stripped out later on when // between block elements, and retained when between inline elements. let fragment_info = SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(" ".to_owned())); + UnscannedTextFragmentInfo::new(" ".to_owned(), None)); properties::modify_style_for_replaced_content(&mut whitespace_style); properties::modify_style_for_text(&mut whitespace_style); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, @@ -714,31 +714,44 @@ impl<'a> FlowConstructor<'a> { return } + let insertion_point = node.insertion_point(); let mut style = (*style).clone(); properties::modify_style_for_text(&mut style); - for content_item in text_content { - let specific = match content_item { - ContentItem::String(string) => { - let info = UnscannedTextFragmentInfo::from_text(string); - SpecificFragmentInfo::UnscannedText(info) - } - content_item => { - let content_item = box GeneratedContentInfo::ContentItem(content_item); - SpecificFragmentInfo::GeneratedContent(content_item) - } - }; - fragments.fragments - .push_back(Fragment::from_opaque_node_and_style(node.opaque(), - node.get_pseudo_element_type() - .strip(), - style.clone(), - node.restyle_damage(), - specific)) + match text_content { + TextContent::Text(string) => { + let info = UnscannedTextFragmentInfo::new(string, insertion_point); + let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info); + fragments.fragments.push_back(Fragment::from_opaque_node_and_style( + node.opaque(), + node.get_pseudo_element_type().strip(), + style.clone(), + node.restyle_damage(), + specific_fragment_info)) + } + TextContent::GeneratedContent(content_items) => { + for content_item in content_items.into_iter() { + let specific_fragment_info = match content_item { + ContentItem::String(string) => { + let info = UnscannedTextFragmentInfo::new(string, None); + SpecificFragmentInfo::UnscannedText(info) + } + content_item => { + let content_item = box GeneratedContentInfo::ContentItem(content_item); + SpecificFragmentInfo::GeneratedContent(content_item) + } + }; + fragments.fragments.push_back(Fragment::from_opaque_node_and_style( + node.opaque(), + node.get_pseudo_element_type().strip(), + style.clone(), + node.restyle_damage(), + specific_fragment_info)) + } + } } } - /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed /// to happen. @@ -851,7 +864,7 @@ impl<'a> FlowConstructor<'a> { whitespace_damage)) => { // Instantiate the whitespace fragment. let fragment_info = SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(" ".to_owned())); + UnscannedTextFragmentInfo::new(" ".to_owned(), None)); properties::modify_style_for_replaced_content(&mut whitespace_style); properties::modify_style_for_text(&mut whitespace_style); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, @@ -1199,7 +1212,7 @@ impl<'a> FlowConstructor<'a> { unscanned_marker_fragments.push_back(Fragment::new( node, SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(text)))); + UnscannedTextFragmentInfo::new(text, None)))); let marker_fragments = TextRunScanner::new().scan_for_runs( &mut self.layout_context.font_context(), unscanned_marker_fragments); @@ -1793,7 +1806,7 @@ fn control_chars_to_fragment(node: &InlineFragmentNodeInfo, restyle_damage: RestyleDamage) -> Fragment { let info = SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(String::from(text))); + UnscannedTextFragmentInfo::new(String::from(text), None)); let mut style = node.style.clone(); properties::modify_style_for_text(&mut style); Fragment::from_opaque_node_and_style(node.address, diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index a7ab373afdb..f73e6013c57 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -651,6 +651,11 @@ pub struct ScannedTextFragmentInfo { /// The intrinsic size of the text fragment. pub content_size: LogicalSize, + /// The position of the insertion point in characters, if any. + /// + /// TODO(pcwalton): Make this a range. + pub insertion_point: Option, + /// The range within the above text run that this represents. pub range: Range, @@ -669,11 +674,13 @@ impl ScannedTextFragmentInfo { pub fn new(run: Arc, range: Range, content_size: LogicalSize, + insertion_point: &Option, requires_line_break_afterward_if_wrapping_on_newlines: bool) -> ScannedTextFragmentInfo { ScannedTextFragmentInfo { run: run, range: range, + insertion_point: *insertion_point, content_size: content_size, range_end_including_stripped_whitespace: range.end(), requires_line_break_afterward_if_wrapping_on_newlines: @@ -726,14 +733,20 @@ pub struct TruncationResult { pub struct UnscannedTextFragmentInfo { /// The text inside the fragment. pub text: Box, + + /// The position of the insertion point, if any. + /// + /// TODO(pcwalton): Make this a range. + pub insertion_point: Option, } impl UnscannedTextFragmentInfo { /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. #[inline] - pub fn from_text(text: String) -> UnscannedTextFragmentInfo { + pub fn new(text: String, insertion_point: Option) -> UnscannedTextFragmentInfo { UnscannedTextFragmentInfo { text: text.into_boxed_str(), + insertion_point: insertion_point, } } } @@ -843,10 +856,12 @@ impl Fragment { self.border_box.size.block); let requires_line_break_afterward_if_wrapping_on_newlines = self.requires_line_break_afterward_if_wrapping_on_newlines(); + // FIXME(pcwalton): This should modify the insertion point as necessary. let info = box ScannedTextFragmentInfo::new( text_run, split.range, size, + &None, requires_line_break_afterward_if_wrapping_on_newlines); self.transform(size, SpecificFragmentInfo::ScannedText(info)) } @@ -856,8 +871,8 @@ impl Fragment { let mut unscanned_ellipsis_fragments = LinkedList::new(); unscanned_ellipsis_fragments.push_back(self.transform( self.border_box.size, - SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text( - "…".to_owned())))); + SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new("…".to_owned(), + None)))); let ellipsis_fragments = TextRunScanner::new().scan_for_runs(&mut layout_context.font_context(), unscanned_ellipsis_fragments); debug_assert!(ellipsis_fragments.len() == 1); diff --git a/components/layout/generated_content.rs b/components/layout/generated_content.rs index 316f012b885..2e823d3bc1d 100644 --- a/components/layout/generated_content.rs +++ b/components/layout/generated_content.rs @@ -428,7 +428,7 @@ fn render_text(layout_context: &LayoutContext, string: String) -> Option { let mut fragments = LinkedList::new(); - let info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text(string)); + let info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new(string, None)); fragments.push_back(Fragment::from_opaque_node_and_style(node, pseudo, style, diff --git a/components/layout/text.rs b/components/layout/text.rs index 856017c7f56..4657dc86e23 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -176,9 +176,12 @@ impl TextRunScanner { let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new()); for (fragment_index, in_fragment) in self.clump.iter().enumerate() { let mut mapping = RunMapping::new(&run_info_list[..], &run_info, fragment_index); - let text = match in_fragment.specific { + let text; + let insertion_point; + match in_fragment.specific { SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => { - &text_fragment_info.text + text = &text_fragment_info.text; + insertion_point = text_fragment_info.insertion_point; } _ => panic!("Expected an unscanned text fragment!"), }; @@ -208,6 +211,7 @@ impl TextRunScanner { mapping.flush(&mut mappings, &mut run_info, &**text, + insertion_point, compression, text_transform, &mut last_whitespace, @@ -239,6 +243,7 @@ impl TextRunScanner { mapping.flush(&mut mappings, &mut run_info, &**text, + insertion_point, compression, text_transform, &mut last_whitespace, @@ -275,7 +280,13 @@ impl TextRunScanner { options.flags.insert(RTL_FLAG); } let mut font = fontgroup.fonts.get(run_info.font_index).unwrap().borrow_mut(); - Arc::new(TextRun::new(&mut *font, run_info.text, &options, run_info.bidi_level)) + ScannedTextRun { + run: Arc::new(TextRun::new(&mut *font, + run_info.text, + &options, + run_info.bidi_level)), + insertion_point: run_info.insertion_point, + } }).collect::>() }; @@ -296,19 +307,20 @@ impl TextRunScanner { }; let mut mapping = mappings.next().unwrap(); - let run = runs[mapping.text_run_index].clone(); + let scanned_run = runs[mapping.text_run_index].clone(); let requires_line_break_afterward_if_wrapping_on_newlines = - run.text.char_at_reverse(mapping.byte_range.end()) == '\n'; + scanned_run.run.text.char_at_reverse(mapping.byte_range.end()) == '\n'; if requires_line_break_afterward_if_wrapping_on_newlines { mapping.char_range.extend_by(CharIndex(-1)); } let text_size = old_fragment.border_box.size; let mut new_text_fragment_info = box ScannedTextFragmentInfo::new( - run, + scanned_run.run, mapping.char_range, text_size, + &scanned_run.insertion_point, requires_line_break_afterward_if_wrapping_on_newlines); let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range); @@ -382,6 +394,7 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList { @@ -403,12 +416,14 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList, /// The index of the applicable font in the font group. font_index: usize, /// A cached copy of the number of Unicode characters in the text run. @@ -430,6 +447,7 @@ impl RunInfo { fn new() -> RunInfo { RunInfo { text: String::new(), + insertion_point: None, font_index: 0, character_length: 0, bidi_level: 0, @@ -472,6 +490,7 @@ impl RunMapping { mappings: &mut Vec, run_info: &mut RunInfo, text: &str, + insertion_point: Option, compression: CompressionMode, text_transform: text_transform::T, last_whitespace: &mut bool, @@ -489,6 +508,12 @@ impl RunMapping { old_byte_length, text_transform); + // Record the position of the insertion point if necessary. + if let Some(insertion_point) = insertion_point { + run_info.insertion_point = + Some(CharIndex(run_info.character_length as isize + insertion_point.0)) + } + run_info.character_length = run_info.character_length + character_count; *start_position = end_position; @@ -570,3 +595,9 @@ fn apply_style_transform_if_necessary(string: &mut String, } } +#[derive(Clone)] +struct ScannedTextRun { + run: Arc, + insertion_point: Option, +} + diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index d346e97a40e..8dfa9907770 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -37,6 +37,7 @@ use incremental::RestyleDamage; use opaque_node::OpaqueNodeMethods; use gfx::display_list::OpaqueNode; +use gfx::text::glyph::CharIndex; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::{PipelineId, SubpageId}; use script::dom::attr::AttrValue; @@ -904,7 +905,7 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { /// its content. Otherwise, panics. /// /// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this. - pub fn text_content(&self) -> Vec { + pub fn text_content(&self) -> TextContent { if self.pseudo != PseudoElementType::Normal { let layout_data_ref = self.borrow_layout_data(); let data = &layout_data_ref.as_ref().unwrap().data; @@ -915,8 +916,10 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { &data.after_style }; return match style.as_ref().unwrap().get_box().content { - content::T::Content(ref value) if !value.is_empty() => (*value).clone(), - _ => vec![], + content::T::Content(ref value) if !value.is_empty() => { + TextContent::GeneratedContent((*value).clone()) + } + _ => TextContent::GeneratedContent(vec![]), }; } @@ -926,22 +929,48 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { let data = unsafe { CharacterDataCast::from_layout_js(&text).data_for_layout().to_owned() }; - return vec![ContentItem::String(data)]; + return TextContent::Text(data); } let input = HTMLInputElementCast::to_layout_js(this); if let Some(input) = input { let data = unsafe { input.get_value_for_layout() }; - return vec![ContentItem::String(data)]; + return TextContent::Text(data); } let area = HTMLTextAreaElementCast::to_layout_js(this); if let Some(area) = area { let data = unsafe { area.get_value_for_layout() }; - return vec![ContentItem::String(data)]; + return TextContent::Text(data); } panic!("not text!") } + /// If the insertion point is within this node, returns it. Otherwise, returns `None`. + pub fn insertion_point(&self) -> Option { + let this = unsafe { + self.get_jsmanaged() + }; + let input = HTMLInputElementCast::to_layout_js(this); + if let Some(input) = input { + let insertion_point = unsafe { + input.get_insertion_point_for_layout() + }; + let text = unsafe { + input.get_value_for_layout() + }; + + let mut character_count = 0; + for (character_index, _) in text.char_indices() { + if character_index == insertion_point.index { + return Some(CharIndex(character_count)) + } + character_count += 1 + } + return Some(CharIndex(character_count)) + } + None + } + /// If this is an image element, returns its URL. If this is not an image element, fails. /// /// FIXME(pcwalton): Don't copy URLs. @@ -1090,3 +1119,18 @@ pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> La let (node, _) = *node; mem::transmute(node) } + +pub enum TextContent { + Text(String), + GeneratedContent(Vec), +} + +impl TextContent { + pub fn is_empty(&self) -> bool { + match *self { + TextContent::Text(_) => false, + TextContent::GeneratedContent(ref content) => content.is_empty(), + } + } +} + diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index f96c89e7dfa..51b9a3c54f0 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -144,6 +144,8 @@ pub trait LayoutHTMLInputElementHelpers { unsafe fn get_value_for_layout(self) -> String; #[allow(unsafe_code)] unsafe fn get_size_for_layout(self) -> u32; + #[allow(unsafe_code)] + unsafe fn get_insertion_point_for_layout(self) -> TextPoint; } pub trait RawLayoutHTMLInputElementHelpers { @@ -194,6 +196,12 @@ impl LayoutHTMLInputElementHelpers for LayoutJS { unsafe fn get_size_for_layout(self) -> u32 { (*self.unsafe_get()).get_size_for_layout() } + + #[allow(unrooted_must_root)] + #[allow(unsafe_code)] + unsafe fn get_insertion_point_for_layout(self) -> TextPoint { + (*self.unsafe_get()).textinput.borrow_for_layout().edit_point + } } impl RawLayoutHTMLInputElementHelpers for HTMLInputElement {