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:
bors-servo 2016-03-26 08:16:20 +05:30
commit bed91b3334
17 changed files with 264 additions and 77 deletions

View file

@ -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(),

View file

@ -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);

View file

@ -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,
}

View file

@ -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)

View file

@ -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,
}
}

View file

@ -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

View file

@ -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"

View file

@ -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)]

View file

@ -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
}

View file

@ -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;

View file

@ -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 {

View file

@ -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
View file

@ -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
View file

@ -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)",

View file

@ -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",

View 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>

View 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>