aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2016-03-26 08:16:20 +0530
committerbors-servo <lbergstrom+bors@mozilla.com>2016-03-26 08:16:20 +0530
commitbed91b3334786970c91a47c3bc95889d8675b4d5 (patch)
treea4f91a1957a33d18b393b41edab354a113720f5d
parent1554331f06900e69f246ed9986a08aae91a0a71e (diff)
parent08caf7412fc3e86a76c47876eb9bfab9804f2182 (diff)
downloadservo-bed91b3334786970c91a47c3bc95889d8675b4d5.tar.gz
servo-bed91b3334786970c91a47c3bc95889d8675b4d5.zip
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 -->
-rw-r--r--components/layout/construct.rs4
-rw-r--r--components/layout/display_list_builder.rs27
-rw-r--r--components/layout/fragment.rs62
-rw-r--r--components/layout/inline.rs1
-rw-r--r--components/layout/text.rs75
-rw-r--r--components/layout/wrapper.rs18
-rw-r--r--components/script/Cargo.toml1
-rw-r--r--components/script/dom/htmlinputelement.rs38
-rw-r--r--components/script/dom/htmltextareaelement.rs7
-rw-r--r--components/script/lib.rs1
-rw-r--r--components/script/textinput.rs10
-rw-r--r--components/servo/Cargo.lock1
-rw-r--r--ports/cef/Cargo.lock1
-rw-r--r--ports/gonk/Cargo.lock1
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json48
-rw-r--r--tests/wpt/mozilla/tests/css/input_selection_a.html28
-rw-r--r--tests/wpt/mozilla/tests/css/input_selection_ref.html18
17 files changed, 264 insertions, 77 deletions
diff --git a/components/layout/construct.rs b/components/layout/construct.rs
index aedbeaf04f6..c964501116c 100644
--- a/components/layout/construct.rs
+++ b/components/layout/construct.rs
@@ -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(),
diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs
index d20186cf0f9..de6e707169c 100644
--- a/components/layout/display_list_builder.rs
+++ b/components/layout/display_list_builder.rs
@@ -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);
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
index 5bc8c506d55..ba7ff2f714e 100644
--- a/components/layout/fragment.rs
+++ b/components/layout/fragment.rs
@@ -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,
}
diff --git a/components/layout/inline.rs b/components/layout/inline.rs
index 175295c6f09..cd2a822f317 100644
--- a/components/layout/inline.rs
+++ b/components/layout/inline.rs
@@ -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)
diff --git a/components/layout/text.rs b/components/layout/text.rs
index 0969bc4e43e..14b0d138113 100644
--- a/components/layout/text.rs
+++ b/components/layout/text.rs
@@ -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(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);
}
- Some(_) | None => {
- insertion_point_before = unscanned_text_fragment_info.insertion_point;
- unscanned_text_fragment_info.insertion_point = None;
+ _ => {
+ // 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,
}
}
diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs
index 094bc40a4a0..eb0176ddacd 100644
--- a/components/layout/wrapper.rs
+++ b/components/layout/wrapper.rs
@@ -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
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
index cb3a8d81181..bf255df7362 100644
--- a/components/script/Cargo.toml
+++ b/components/script/Cargo.toml
@@ -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"
diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
index d003456e648..f75fd670465 100644
--- a/components/script/dom/htmlinputelement.rs
+++ b/components/script/dom/htmlinputelement.rs
@@ -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)]
diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs
index c2289e176da..0f82919124f 100644
--- a/components/script/dom/htmltextareaelement.rs
+++ b/components/script/dom/htmltextareaelement.rs
@@ -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
}
diff --git a/components/script/lib.rs b/components/script/lib.rs
index 99a3bb7ce9b..feb7a5fee26 100644
--- a/components/script/lib.rs
+++ b/components/script/lib.rs
@@ -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;
diff --git a/components/script/textinput.rs b/components/script/textinput.rs
index 35b49ee57a4..343b114bf2d 100644
--- a/components/script/textinput.rs
+++ b/components/script/textinput.rs
@@ -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 {
diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock
index 5606eea216e..583381d049b 100644
--- a/components/servo/Cargo.lock
+++ b/components/servo/Cargo.lock
@@ -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)",
diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock
index 9387c95cd1d..463f3e92a99 100644
--- a/ports/cef/Cargo.lock
+++ b/ports/cef/Cargo.lock
@@ -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)",
diff --git a/ports/gonk/Cargo.lock b/ports/gonk/Cargo.lock
index f6b0bab26e6..3f1b057dc5f 100644
--- a/ports/gonk/Cargo.lock
+++ b/ports/gonk/Cargo.lock
@@ -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)",
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index 5f653e5a0e1..a72ebe0e24b 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -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",
diff --git a/tests/wpt/mozilla/tests/css/input_selection_a.html b/tests/wpt/mozilla/tests/css/input_selection_a.html
new file mode 100644
index 00000000000..0e923f9425f
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/input_selection_a.html
@@ -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>
diff --git a/tests/wpt/mozilla/tests/css/input_selection_ref.html b/tests/wpt/mozilla/tests/css/input_selection_ref.html
new file mode 100644
index 00000000000..6903f7d9118
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/input_selection_ref.html
@@ -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>