diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-03-26 08:16:20 +0530 |
---|---|---|
committer | bors-servo <lbergstrom+bors@mozilla.com> | 2016-03-26 08:16:20 +0530 |
commit | bed91b3334786970c91a47c3bc95889d8675b4d5 (patch) | |
tree | a4f91a1957a33d18b393b41edab354a113720f5d | |
parent | 1554331f06900e69f246ed9986a08aae91a0a71e (diff) | |
parent | 08caf7412fc3e86a76c47876eb9bfab9804f2182 (diff) | |
download | servo-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.rs | 4 | ||||
-rw-r--r-- | components/layout/display_list_builder.rs | 27 | ||||
-rw-r--r-- | components/layout/fragment.rs | 62 | ||||
-rw-r--r-- | components/layout/inline.rs | 1 | ||||
-rw-r--r-- | components/layout/text.rs | 75 | ||||
-rw-r--r-- | components/layout/wrapper.rs | 18 | ||||
-rw-r--r-- | components/script/Cargo.toml | 1 | ||||
-rw-r--r-- | components/script/dom/htmlinputelement.rs | 38 | ||||
-rw-r--r-- | components/script/dom/htmltextareaelement.rs | 7 | ||||
-rw-r--r-- | components/script/lib.rs | 1 | ||||
-rw-r--r-- | components/script/textinput.rs | 10 | ||||
-rw-r--r-- | components/servo/Cargo.lock | 1 | ||||
-rw-r--r-- | ports/cef/Cargo.lock | 1 | ||||
-rw-r--r-- | ports/gonk/Cargo.lock | 1 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 48 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/css/input_selection_a.html | 28 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/css/input_selection_ref.html | 18 |
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> |