mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Auto merge of #10176 - mbrubeck:selection-range, r=pcwalton
Highlight selected text in input fields Fixes #9993. This does not yet allow stylesheets to set the selection colors; instead it uses a hard-coded orange background and white foreground. r? @pcwalton <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10176) <!-- Reviewable:end -->
This commit is contained in:
commit
bed91b3334
17 changed files with 264 additions and 77 deletions
|
@ -709,13 +709,13 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
|
|||
return
|
||||
}
|
||||
|
||||
let insertion_point = node.insertion_point();
|
||||
let selection = node.selection();
|
||||
let mut style = (*style).clone();
|
||||
properties::modify_style_for_text(&mut style);
|
||||
|
||||
match text_content {
|
||||
TextContent::Text(string) => {
|
||||
let info = UnscannedTextFragmentInfo::new(string, insertion_point);
|
||||
let info = UnscannedTextFragmentInfo::new(string, selection);
|
||||
let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info);
|
||||
fragments.fragments.push_back(Fragment::from_opaque_node_and_style(
|
||||
node.opaque(),
|
||||
|
|
|
@ -104,6 +104,10 @@ impl<'a> DisplayListBuildState<'a> {
|
|||
/// The logical width of an insertion point: at the moment, a one-pixel-wide line.
|
||||
const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX);
|
||||
|
||||
// Colors for selected text. TODO (#8077): Use the ::selection pseudo-element to set these.
|
||||
const SELECTION_FOREGROUND_COLOR: RGBA = RGBA { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 };
|
||||
const SELECTION_BACKGROUND_COLOR: RGBA = RGBA { red: 1.0, green: 0.5, blue: 0.0, alpha: 1.0 };
|
||||
|
||||
// TODO(gw): The transforms spec says that perspective length must
|
||||
// be positive. However, there is some confusion between the spec
|
||||
// and browser implementations as to handling the case of 0 for the
|
||||
|
@ -922,6 +926,23 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Draw a highlighted background if the text is selected.
|
||||
//
|
||||
// TODO: Allow non-text fragments to be selected too.
|
||||
if scanned_text_fragment_info.selected() {
|
||||
state.add_display_item(
|
||||
DisplayItem::SolidColorClass(box SolidColorDisplayItem {
|
||||
base: BaseDisplayItem::new(stacking_relative_border_box,
|
||||
DisplayItemMetadata::new(self.node,
|
||||
&*self.style,
|
||||
Cursor::DefaultCursor),
|
||||
&clip),
|
||||
color: SELECTION_BACKGROUND_COLOR.to_gfx_color()
|
||||
}), display_list_section);
|
||||
}
|
||||
|
||||
// Draw a caret at the insertion point.
|
||||
let insertion_point_index = match scanned_text_fragment_info.insertion_point {
|
||||
Some(insertion_point_index) => insertion_point_index,
|
||||
None => return,
|
||||
|
@ -1095,7 +1116,11 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
//
|
||||
// NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
|
||||
// to back).
|
||||
let text_color = self.style().get_color().color;
|
||||
let text_color = if text_fragment.selected() {
|
||||
SELECTION_FOREGROUND_COLOR
|
||||
} else {
|
||||
self.style().get_color().color
|
||||
};
|
||||
for text_shadow in self.style.get_effects().text_shadow.0.iter().rev() {
|
||||
let offset = &Point2D::new(text_shadow.offset_x, text_shadow.offset_y);
|
||||
let color = self.style().resolve_color(text_shadow.color);
|
||||
|
|
|
@ -655,8 +655,6 @@ pub struct ScannedTextFragmentInfo {
|
|||
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.
|
||||
|
@ -667,9 +665,18 @@ pub struct ScannedTextFragmentInfo {
|
|||
/// performing incremental reflow.
|
||||
pub range_end_including_stripped_whitespace: CharIndex,
|
||||
|
||||
/// Whether a line break is required after this fragment if wrapping on newlines (e.g. if
|
||||
/// `white-space: pre` is in effect).
|
||||
pub requires_line_break_afterward_if_wrapping_on_newlines: bool,
|
||||
pub flags: ScannedTextFlags,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
flags ScannedTextFlags: u8 {
|
||||
/// Whether a line break is required after this fragment if wrapping on newlines (e.g. if
|
||||
/// `white-space: pre` is in effect).
|
||||
const REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES = 0x01,
|
||||
|
||||
/// Is this fragment selected?
|
||||
const SELECTED = 0x02,
|
||||
}
|
||||
}
|
||||
|
||||
impl ScannedTextFragmentInfo {
|
||||
|
@ -677,19 +684,26 @@ 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)
|
||||
insertion_point: Option<CharIndex>,
|
||||
flags: ScannedTextFlags)
|
||||
-> ScannedTextFragmentInfo {
|
||||
ScannedTextFragmentInfo {
|
||||
run: run,
|
||||
range: range,
|
||||
insertion_point: *insertion_point,
|
||||
insertion_point: insertion_point,
|
||||
content_size: content_size,
|
||||
range_end_including_stripped_whitespace: range.end(),
|
||||
requires_line_break_afterward_if_wrapping_on_newlines:
|
||||
requires_line_break_afterward_if_wrapping_on_newlines,
|
||||
flags: flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
|
||||
self.flags.contains(REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES)
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> bool {
|
||||
self.flags.contains(SELECTED)
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes how to split a fragment. This is used during line breaking as part of the return
|
||||
|
@ -737,19 +751,17 @@ 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>,
|
||||
/// The selected text range. An empty range represents the insertion point.
|
||||
pub selection: Option<Range<CharIndex>>,
|
||||
}
|
||||
|
||||
impl UnscannedTextFragmentInfo {
|
||||
/// Creates a new instance of `UnscannedTextFragmentInfo` from the given text.
|
||||
#[inline]
|
||||
pub fn new(text: String, insertion_point: Option<CharIndex>) -> UnscannedTextFragmentInfo {
|
||||
pub fn new(text: String, selection: Option<Range<CharIndex>>) -> UnscannedTextFragmentInfo {
|
||||
UnscannedTextFragmentInfo {
|
||||
text: text.into_boxed_str(),
|
||||
insertion_point: insertion_point,
|
||||
selection: selection,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -865,15 +877,17 @@ impl Fragment {
|
|||
let size = LogicalSize::new(self.style.writing_mode,
|
||||
split.inline_size,
|
||||
self.border_box.size.block);
|
||||
let requires_line_break_afterward_if_wrapping_on_newlines =
|
||||
self.requires_line_break_afterward_if_wrapping_on_newlines();
|
||||
let flags = match self.specific {
|
||||
SpecificFragmentInfo::ScannedText(ref info) => info.flags,
|
||||
_ => ScannedTextFlags::empty()
|
||||
};
|
||||
// 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);
|
||||
None,
|
||||
flags);
|
||||
self.transform(size, SpecificFragmentInfo::ScannedText(info))
|
||||
}
|
||||
|
||||
|
@ -1681,9 +1695,9 @@ impl Fragment {
|
|||
this_info.range.extend_to(other_info.range_end_including_stripped_whitespace);
|
||||
this_info.content_size.inline =
|
||||
this_info.run.metrics_for_range(&this_info.range).advance_width;
|
||||
this_info.requires_line_break_afterward_if_wrapping_on_newlines =
|
||||
this_info.requires_line_break_afterward_if_wrapping_on_newlines ||
|
||||
other_info.requires_line_break_afterward_if_wrapping_on_newlines;
|
||||
if other_info.requires_line_break_afterward_if_wrapping_on_newlines() {
|
||||
this_info.flags.insert(REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES);
|
||||
}
|
||||
self.border_padding.inline_end = next_fragment.border_padding.inline_end;
|
||||
self.border_box.size.inline = this_info.content_size.inline +
|
||||
self.border_padding.inline_start_end();
|
||||
|
@ -2247,7 +2261,7 @@ impl Fragment {
|
|||
pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
|
||||
match self.specific {
|
||||
SpecificFragmentInfo::ScannedText(ref scanned_text) => {
|
||||
scanned_text.requires_line_break_afterward_if_wrapping_on_newlines
|
||||
scanned_text.requires_line_break_afterward_if_wrapping_on_newlines()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
@ -354,6 +354,7 @@ impl LineBreaker {
|
|||
let need_to_merge = match (&mut result.specific, &candidate.specific) {
|
||||
(&mut SpecificFragmentInfo::ScannedText(ref mut result_info),
|
||||
&SpecificFragmentInfo::ScannedText(ref candidate_info)) => {
|
||||
result_info.selected() == candidate_info.selected() &&
|
||||
util::arc_ptr_eq(&result_info.run, &candidate_info.run) &&
|
||||
inline_contexts_are_equal(&result.inline_context,
|
||||
&candidate.inline_context)
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
#![deny(unsafe_code)]
|
||||
|
||||
use app_units::Au;
|
||||
use fragment::{Fragment, ScannedTextFragmentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
|
||||
use fragment::{Fragment, REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES, ScannedTextFlags};
|
||||
use fragment::{ScannedTextFragmentInfo, SELECTED, SpecificFragmentInfo, UnscannedTextFragmentInfo};
|
||||
use gfx::font::{DISABLE_KERNING_SHAPING_FLAG, FontMetrics, IGNORE_LIGATURES_SHAPING_FLAG};
|
||||
use gfx::font::{RTL_FLAG, RunMetrics, ShapingFlags, ShapingOptions};
|
||||
use gfx::font_context::FontContext;
|
||||
|
@ -172,17 +173,21 @@ impl TextRunScanner {
|
|||
for (fragment_index, in_fragment) in self.clump.iter().enumerate() {
|
||||
let mut mapping = RunMapping::new(&run_info_list[..], &run_info, fragment_index);
|
||||
let text;
|
||||
let insertion_point;
|
||||
let selection;
|
||||
match in_fragment.specific {
|
||||
SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => {
|
||||
text = &text_fragment_info.text;
|
||||
insertion_point = text_fragment_info.insertion_point;
|
||||
selection = text_fragment_info.selection;
|
||||
}
|
||||
_ => panic!("Expected an unscanned text fragment!"),
|
||||
};
|
||||
let insertion_point = match selection {
|
||||
Some(range) if range.is_empty() => Some(range.begin()),
|
||||
_ => None
|
||||
};
|
||||
|
||||
let (mut start_position, mut end_position) = (0, 0);
|
||||
for character in text.chars() {
|
||||
for (char_index, character) in text.chars().enumerate() {
|
||||
// Search for the first font in this font group that contains a glyph for this
|
||||
// character.
|
||||
let mut font_index = 0;
|
||||
|
@ -213,11 +218,18 @@ impl TextRunScanner {
|
|||
run_info.script = script;
|
||||
}
|
||||
|
||||
let selected = match selection {
|
||||
Some(range) => range.contains(CharIndex(char_index as isize)),
|
||||
None => false
|
||||
};
|
||||
|
||||
// Now, if necessary, flush the mapping we were building up.
|
||||
if run_info.font_index != font_index ||
|
||||
run_info.bidi_level != bidi_level ||
|
||||
!compatible_script
|
||||
{
|
||||
let flush_run = run_info.font_index != font_index ||
|
||||
run_info.bidi_level != bidi_level ||
|
||||
!compatible_script;
|
||||
let flush_mapping = flush_run || mapping.selected != selected;
|
||||
|
||||
if flush_mapping {
|
||||
if end_position > start_position {
|
||||
mapping.flush(&mut mappings,
|
||||
&mut run_info,
|
||||
|
@ -230,8 +242,10 @@ impl TextRunScanner {
|
|||
end_position);
|
||||
}
|
||||
if run_info.text.len() > 0 {
|
||||
run_info_list.push(run_info);
|
||||
run_info = RunInfo::new();
|
||||
if flush_run {
|
||||
run_info_list.push(run_info);
|
||||
run_info = RunInfo::new();
|
||||
}
|
||||
mapping = RunMapping::new(&run_info_list[..],
|
||||
&run_info,
|
||||
fragment_index);
|
||||
|
@ -239,6 +253,7 @@ impl TextRunScanner {
|
|||
run_info.font_index = font_index;
|
||||
run_info.bidi_level = bidi_level;
|
||||
run_info.script = script;
|
||||
mapping.selected = selected;
|
||||
}
|
||||
|
||||
// Consume this character.
|
||||
|
@ -330,12 +345,20 @@ impl TextRunScanner {
|
|||
}
|
||||
|
||||
let text_size = old_fragment.border_box.size;
|
||||
|
||||
let mut flags = ScannedTextFlags::empty();
|
||||
if mapping.selected {
|
||||
flags.insert(SELECTED);
|
||||
}
|
||||
if requires_line_break_afterward_if_wrapping_on_newlines {
|
||||
flags.insert(REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES);
|
||||
}
|
||||
let mut new_text_fragment_info = box ScannedTextFragmentInfo::new(
|
||||
scanned_run.run,
|
||||
mapping.char_range,
|
||||
text_size,
|
||||
&scanned_run.insertion_point,
|
||||
requires_line_break_afterward_if_wrapping_on_newlines);
|
||||
scanned_run.insertion_point,
|
||||
flags);
|
||||
|
||||
let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range);
|
||||
let writing_mode = old_fragment.style.writing_mode;
|
||||
|
@ -408,7 +431,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 selection_before;
|
||||
{
|
||||
if !first_fragment.white_space().preserve_newlines() {
|
||||
return;
|
||||
|
@ -433,21 +456,28 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragm
|
|||
unscanned_text_fragment_info.text =
|
||||
unscanned_text_fragment_info.text[(position + 1)..].to_owned().into_boxed_str();
|
||||
let offset = CharIndex(string_before.char_indices().count() as isize);
|
||||
match unscanned_text_fragment_info.insertion_point {
|
||||
Some(insertion_point) if insertion_point >= offset => {
|
||||
insertion_point_before = None;
|
||||
unscanned_text_fragment_info.insertion_point = Some(insertion_point - offset);
|
||||
match unscanned_text_fragment_info.selection {
|
||||
Some(ref mut selection) if selection.begin() >= offset => {
|
||||
// Selection is entirely in the second fragment.
|
||||
selection_before = None;
|
||||
selection.shift_by(-offset);
|
||||
}
|
||||
Some(_) | None => {
|
||||
insertion_point_before = unscanned_text_fragment_info.insertion_point;
|
||||
unscanned_text_fragment_info.insertion_point = None;
|
||||
Some(ref mut selection) if selection.end() > offset => {
|
||||
// Selection is split across two fragments.
|
||||
selection_before = Some(Range::new(selection.begin(), offset));
|
||||
*selection = Range::new(CharIndex(0), selection.end() - offset);
|
||||
}
|
||||
_ => {
|
||||
// Selection is entirely in the first fragment.
|
||||
selection_before = unscanned_text_fragment_info.selection;
|
||||
unscanned_text_fragment_info.selection = None;
|
||||
}
|
||||
};
|
||||
}
|
||||
first_fragment.transform(first_fragment.border_box.size,
|
||||
SpecificFragmentInfo::UnscannedText(
|
||||
UnscannedTextFragmentInfo::new(string_before,
|
||||
insertion_point_before)))
|
||||
selection_before)))
|
||||
};
|
||||
|
||||
fragments.push_front(new_fragment);
|
||||
|
@ -494,6 +524,8 @@ struct RunMapping {
|
|||
old_fragment_index: usize,
|
||||
/// The index of the text run we're going to create.
|
||||
text_run_index: usize,
|
||||
/// Is the text in this fragment selected?
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl RunMapping {
|
||||
|
@ -508,6 +540,7 @@ impl RunMapping {
|
|||
byte_range: Range::new(0, 0),
|
||||
old_fragment_index: fragment_index,
|
||||
text_run_index: run_info_list.len(),
|
||||
selected: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ use gfx::text::glyph::CharIndex;
|
|||
use incremental::RestyleDamage;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use opaque_node::OpaqueNodeMethods;
|
||||
use range::{Range, RangeIndex};
|
||||
use script::dom::attr::AttrValue;
|
||||
use script::dom::bindings::inheritance::{Castable, CharacterDataTypeId, ElementTypeId};
|
||||
use script::dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId};
|
||||
|
@ -826,7 +827,7 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized + PartialEq {
|
|||
fn text_content(&self) -> TextContent;
|
||||
|
||||
/// If the insertion point is within this node, returns it. Otherwise, returns `None`.
|
||||
fn insertion_point(&self) -> Option<CharIndex>;
|
||||
fn selection(&self) -> Option<Range<CharIndex>>;
|
||||
|
||||
/// If this is an image element, returns its URL. If this is not an image element, fails.
|
||||
///
|
||||
|
@ -1050,21 +1051,24 @@ impl<'ln> ThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> {
|
|||
panic!("not text!")
|
||||
}
|
||||
|
||||
fn insertion_point(&self) -> Option<CharIndex> {
|
||||
fn selection(&self) -> Option<Range<CharIndex>> {
|
||||
let this = unsafe {
|
||||
self.get_jsmanaged()
|
||||
};
|
||||
|
||||
if let Some(area) = this.downcast::<HTMLTextAreaElement>() {
|
||||
if let Some(insertion_point) = unsafe { area.get_absolute_insertion_point_for_layout() } {
|
||||
if let Some(selection) = unsafe { area.get_absolute_selection_for_layout() } {
|
||||
let text = unsafe { area.get_value_for_layout() };
|
||||
return Some(CharIndex(search_index(insertion_point, text.char_indices())));
|
||||
let begin_byte = selection.begin();
|
||||
let begin = search_index(begin_byte, text.char_indices());
|
||||
let length = search_index(selection.length(), text[begin_byte..].char_indices());
|
||||
return Some(Range::new(CharIndex(begin), CharIndex(length)));
|
||||
}
|
||||
}
|
||||
if let Some(input) = this.downcast::<HTMLInputElement>() {
|
||||
let insertion_point_index = unsafe { input.get_insertion_point_index_for_layout() };
|
||||
if let Some(insertion_point_index) = insertion_point_index {
|
||||
return Some(CharIndex(insertion_point_index));
|
||||
if let Some(selection) = unsafe { input.get_selection_for_layout() } {
|
||||
return Some(Range::new(CharIndex(selection.begin()),
|
||||
CharIndex(selection.length())));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
|
@ -82,6 +82,7 @@ num = "0.1.24"
|
|||
rand = "0.3"
|
||||
phf = "0.7.13"
|
||||
phf_macros = "0.7.13"
|
||||
range = { path = "../range" }
|
||||
ref_filter_map = "1.0"
|
||||
ref_slice = "0.1.0"
|
||||
regex = "0.1.43"
|
||||
|
|
|
@ -31,6 +31,7 @@ use dom::nodelist::NodeList;
|
|||
use dom::validation::Validatable;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use msg::constellation_msg::ConstellationChan;
|
||||
use range::Range;
|
||||
use script_thread::ScriptThreadEventCategory::InputEvent;
|
||||
use script_thread::{CommonScriptMsg, Runnable};
|
||||
use script_traits::ScriptMsg as ConstellationMsg;
|
||||
|
@ -209,7 +210,7 @@ pub trait LayoutHTMLInputElementHelpers {
|
|||
#[allow(unsafe_code)]
|
||||
unsafe fn get_size_for_layout(self) -> u32;
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn get_insertion_point_index_for_layout(self) -> Option<isize>;
|
||||
unsafe fn get_selection_for_layout(self) -> Option<Range<isize>>;
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn get_checked_state_for_layout(self) -> bool;
|
||||
#[allow(unsafe_code)]
|
||||
|
@ -242,7 +243,7 @@ impl LayoutHTMLInputElementHelpers for LayoutJS<HTMLInputElement> {
|
|||
InputType::InputPassword => {
|
||||
let text = get_raw_textinput_value(self);
|
||||
if !text.is_empty() {
|
||||
// The implementation of get_insertion_point_index_for_layout expects a 1:1 mapping of chars.
|
||||
// The implementation of get_selection_for_layout expects a 1:1 mapping of chars.
|
||||
text.chars().map(|_| '●').collect()
|
||||
} else {
|
||||
String::from((*self.unsafe_get()).placeholder.borrow_for_layout().clone())
|
||||
|
@ -251,7 +252,7 @@ impl LayoutHTMLInputElementHelpers for LayoutJS<HTMLInputElement> {
|
|||
_ => {
|
||||
let text = get_raw_textinput_value(self);
|
||||
if !text.is_empty() {
|
||||
// The implementation of get_insertion_point_index_for_layout expects a 1:1 mapping of chars.
|
||||
// The implementation of get_selection_for_layout expects a 1:1 mapping of chars.
|
||||
String::from(text)
|
||||
} else {
|
||||
String::from((*self.unsafe_get()).placeholder.borrow_for_layout().clone())
|
||||
|
@ -268,25 +269,24 @@ impl LayoutHTMLInputElementHelpers for LayoutJS<HTMLInputElement> {
|
|||
|
||||
#[allow(unrooted_must_root)]
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn get_insertion_point_index_for_layout(self) -> Option<isize> {
|
||||
unsafe fn get_selection_for_layout(self) -> Option<Range<isize>> {
|
||||
if !(*self.unsafe_get()).upcast::<Element>().get_focus_state() {
|
||||
return None;
|
||||
}
|
||||
match (*self.unsafe_get()).input_type.get() {
|
||||
InputType::InputText => {
|
||||
let raw = self.get_value_for_layout();
|
||||
Some(search_index((*self.unsafe_get()).textinput.borrow_for_layout().edit_point.index,
|
||||
raw.char_indices()))
|
||||
}
|
||||
InputType::InputPassword => {
|
||||
// Use the raw textinput to get the index as long as we use a 1:1 char mapping
|
||||
// in get_input_value_for_layout.
|
||||
let raw = get_raw_textinput_value(self);
|
||||
Some(search_index((*self.unsafe_get()).textinput.borrow_for_layout().edit_point.index,
|
||||
raw.char_indices()))
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
|
||||
// Use the raw textinput to get the index as long as we use a 1:1 char mapping
|
||||
// in get_value_for_layout.
|
||||
let raw = match (*self.unsafe_get()).input_type.get() {
|
||||
InputType::InputText |
|
||||
InputType::InputPassword => get_raw_textinput_value(self),
|
||||
_ => return None
|
||||
};
|
||||
let textinput = (*self.unsafe_get()).textinput.borrow_for_layout();
|
||||
let selection = textinput.get_absolute_selection_range();
|
||||
let begin_byte = selection.begin();
|
||||
let begin = search_index(begin_byte, raw.char_indices());
|
||||
let length = search_index(selection.length(), raw[begin_byte..].char_indices());
|
||||
Some(Range::new(begin, length))
|
||||
}
|
||||
|
||||
#[allow(unrooted_must_root)]
|
||||
|
|
|
@ -26,6 +26,7 @@ use dom::nodelist::NodeList;
|
|||
use dom::validation::Validatable;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use msg::constellation_msg::ConstellationChan;
|
||||
use range::Range;
|
||||
use script_traits::ScriptMsg as ConstellationMsg;
|
||||
use std::cell::Cell;
|
||||
use string_cache::Atom;
|
||||
|
@ -46,7 +47,7 @@ pub trait LayoutHTMLTextAreaElementHelpers {
|
|||
#[allow(unsafe_code)]
|
||||
unsafe fn get_value_for_layout(self) -> String;
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn get_absolute_insertion_point_for_layout(self) -> Option<usize>;
|
||||
unsafe fn get_absolute_selection_for_layout(self) -> Option<Range<usize>>;
|
||||
#[allow(unsafe_code)]
|
||||
fn get_cols(self) -> u32;
|
||||
#[allow(unsafe_code)]
|
||||
|
@ -62,10 +63,10 @@ impl LayoutHTMLTextAreaElementHelpers for LayoutJS<HTMLTextAreaElement> {
|
|||
|
||||
#[allow(unrooted_must_root)]
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn get_absolute_insertion_point_for_layout(self) -> Option<usize> {
|
||||
unsafe fn get_absolute_selection_for_layout(self) -> Option<Range<usize>> {
|
||||
if (*self.unsafe_get()).upcast::<Element>().get_focus_state() {
|
||||
Some((*self.unsafe_get()).textinput.borrow_for_layout()
|
||||
.get_absolute_insertion_point())
|
||||
.get_absolute_selection_range())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ extern crate phf;
|
|||
#[macro_use]
|
||||
extern crate profile_traits;
|
||||
extern crate rand;
|
||||
extern crate range;
|
||||
extern crate ref_filter_map;
|
||||
extern crate ref_slice;
|
||||
extern crate regex;
|
||||
|
|
|
@ -8,6 +8,7 @@ use clipboard_provider::ClipboardProvider;
|
|||
use dom::keyboardevent::{KeyboardEvent, key_value};
|
||||
use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER};
|
||||
use msg::constellation_msg::{Key, KeyModifiers};
|
||||
use range::Range;
|
||||
use std::borrow::ToOwned;
|
||||
use std::cmp::{max, min};
|
||||
use std::default::Default;
|
||||
|
@ -154,6 +155,15 @@ impl<T: ClipboardProvider> TextInput<T> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_absolute_selection_range(&self) -> Range<usize> {
|
||||
match self.get_sorted_selection() {
|
||||
Some((begin, _end)) =>
|
||||
Range::new(self.get_absolute_point_for_text_point(&begin), self.selection_len()),
|
||||
None =>
|
||||
Range::new(self.get_absolute_insertion_point(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_selection_text(&self) -> Option<String> {
|
||||
self.get_sorted_selection().map(|(begin, end)| {
|
||||
if begin.line != end.line {
|
||||
|
|
1
components/servo/Cargo.lock
generated
1
components/servo/Cargo.lock
generated
|
@ -1716,6 +1716,7 @@ dependencies = [
|
|||
"plugins 0.0.1",
|
||||
"profile_traits 0.0.1",
|
||||
"rand 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"range 0.0.1",
|
||||
"ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ref_slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
1
ports/cef/Cargo.lock
generated
1
ports/cef/Cargo.lock
generated
|
@ -1591,6 +1591,7 @@ dependencies = [
|
|||
"plugins 0.0.1",
|
||||
"profile_traits 0.0.1",
|
||||
"rand 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"range 0.0.1",
|
||||
"ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ref_slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
1
ports/gonk/Cargo.lock
generated
1
ports/gonk/Cargo.lock
generated
|
@ -1573,6 +1573,7 @@ dependencies = [
|
|||
"plugins 0.0.1",
|
||||
"profile_traits 0.0.1",
|
||||
"rand 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"range 0.0.1",
|
||||
"ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ref_slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -2540,6 +2540,30 @@
|
|||
"url": "/_mozilla/css/input_placeholder_ref.html"
|
||||
}
|
||||
],
|
||||
"css/input_selection_a.html": [
|
||||
{
|
||||
"path": "css/input_selection_a.html",
|
||||
"references": [
|
||||
[
|
||||
"/_mozilla/css/input_selection_ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
"url": "/_mozilla/css/input_selection_a.html"
|
||||
}
|
||||
],
|
||||
"css/input_selection_ref.html": [
|
||||
{
|
||||
"path": "css/input_selection_ref.html",
|
||||
"references": [
|
||||
[
|
||||
"/_mozilla/css/input_selection_ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
"url": "/_mozilla/css/input_selection_ref.html"
|
||||
}
|
||||
],
|
||||
"css/input_whitespace.html": [
|
||||
{
|
||||
"path": "css/input_whitespace.html",
|
||||
|
@ -8842,6 +8866,30 @@
|
|||
"url": "/_mozilla/css/input_placeholder_ref.html"
|
||||
}
|
||||
],
|
||||
"css/input_selection_a.html": [
|
||||
{
|
||||
"path": "css/input_selection_a.html",
|
||||
"references": [
|
||||
[
|
||||
"/_mozilla/css/input_selection_ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
"url": "/_mozilla/css/input_selection_a.html"
|
||||
}
|
||||
],
|
||||
"css/input_selection_ref.html": [
|
||||
{
|
||||
"path": "css/input_selection_ref.html",
|
||||
"references": [
|
||||
[
|
||||
"/_mozilla/css/input_selection_ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
"url": "/_mozilla/css/input_selection_ref.html"
|
||||
}
|
||||
],
|
||||
"css/input_whitespace.html": [
|
||||
{
|
||||
"path": "css/input_whitespace.html",
|
||||
|
|
28
tests/wpt/mozilla/tests/css/input_selection_a.html
Normal file
28
tests/wpt/mozilla/tests/css/input_selection_a.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>input selection test</title>
|
||||
<link rel="match" href="input_selection_ref.html">
|
||||
<style>
|
||||
input {
|
||||
font: 16px sans-serif;
|
||||
border: 0 none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
::selection {
|
||||
color: white;
|
||||
background: rgba(255, 127, 0, 1.0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input value="Hello">
|
||||
<script>
|
||||
var input = document.querySelector("input");
|
||||
input.focus();
|
||||
input.setSelectionRange(0, 5);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
18
tests/wpt/mozilla/tests/css/input_selection_ref.html
Normal file
18
tests/wpt/mozilla/tests/css/input_selection_ref.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>input selection test</title>
|
||||
<link rel="match" href="input_selection_ref.html">
|
||||
<style>
|
||||
span {
|
||||
font: 16px sans-serif;
|
||||
color: white;
|
||||
background: rgba(255, 128, 0, 1.0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<span>Hello</span>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue