mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
layout: Query and maintain the position of the insertion point
throughout layout for input elements.
This commit is contained in:
parent
34d9a6091b
commit
357419dc8d
6 changed files with 152 additions and 41 deletions
|
@ -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,
|
||||
|
|
|
@ -651,6 +651,11 @@ pub struct ScannedTextFragmentInfo {
|
|||
/// The intrinsic size of the text fragment.
|
||||
pub content_size: LogicalSize<Au>,
|
||||
|
||||
/// The position of the insertion point in characters, if any.
|
||||
///
|
||||
/// TODO(pcwalton): Make this a range.
|
||||
pub insertion_point: Option<CharIndex>,
|
||||
|
||||
/// The range within the above text run that this represents.
|
||||
pub range: Range<CharIndex>,
|
||||
|
||||
|
@ -669,11 +674,13 @@ impl ScannedTextFragmentInfo {
|
|||
pub fn new(run: Arc<TextRun>,
|
||||
range: Range<CharIndex>,
|
||||
content_size: LogicalSize<Au>,
|
||||
insertion_point: &Option<CharIndex>,
|
||||
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<str>,
|
||||
|
||||
/// The position of the insertion point, if any.
|
||||
///
|
||||
/// TODO(pcwalton): Make this a range.
|
||||
pub insertion_point: Option<CharIndex>,
|
||||
}
|
||||
|
||||
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<CharIndex>) -> 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);
|
||||
|
|
|
@ -428,7 +428,7 @@ fn render_text(layout_context: &LayoutContext,
|
|||
string: String)
|
||||
-> Option<SpecificFragmentInfo> {
|
||||
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,
|
||||
|
|
|
@ -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::<Vec<_>>()
|
||||
};
|
||||
|
||||
|
@ -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<Fragm
|
|||
let new_fragment = {
|
||||
let mut first_fragment = fragments.front_mut().unwrap();
|
||||
let string_before;
|
||||
let insertion_point_before;
|
||||
{
|
||||
let unscanned_text_fragment_info = match first_fragment.specific {
|
||||
SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
|
||||
|
@ -403,12 +416,14 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
|
|||
|
||||
string_before =
|
||||
unscanned_text_fragment_info.text[..(position + 1)].to_owned();
|
||||
insertion_point_before = unscanned_text_fragment_info.insertion_point;
|
||||
unscanned_text_fragment_info.text =
|
||||
unscanned_text_fragment_info.text[(position + 1)..].to_owned().into_boxed_str();
|
||||
}
|
||||
first_fragment.transform(first_fragment.border_box.size,
|
||||
SpecificFragmentInfo::UnscannedText(
|
||||
UnscannedTextFragmentInfo::from_text(string_before)))
|
||||
UnscannedTextFragmentInfo::new(string_before,
|
||||
insertion_point_before)))
|
||||
};
|
||||
|
||||
fragments.push_front(new_fragment);
|
||||
|
@ -418,6 +433,8 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
|
|||
struct RunInfo {
|
||||
/// The text that will go in this text run.
|
||||
text: String,
|
||||
/// The insertion point in this text run, if applicable.
|
||||
insertion_point: Option<CharIndex>,
|
||||
/// 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<RunMapping>,
|
||||
run_info: &mut RunInfo,
|
||||
text: &str,
|
||||
insertion_point: Option<CharIndex>,
|
||||
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<TextRun>,
|
||||
insertion_point: Option<CharIndex>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ContentItem> {
|
||||
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<CharIndex> {
|
||||
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<ContentItem>),
|
||||
}
|
||||
|
||||
impl TextContent {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
TextContent::Text(_) => false,
|
||||
TextContent::GeneratedContent(ref content) => content.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HTMLInputElement> {
|
|||
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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue