Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Highlight selected text in input fields #10176

Merged
merged 2 commits into from Mar 26, 2016
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

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.
  • Loading branch information
mbrubeck committed Mar 25, 2016
commit 61710008750f03f1f66ce0b24ea92c2b0286e0e0
@@ -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,10 +655,11 @@ 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>,

/// Is this fragment selected?
pub selected: bool,

This comment has been minimized.

@pcwalton

pcwalton Mar 24, 2016

Contributor

Can we move this down to after requires_line_break_afterward_if_wrapping_on_newlines? (Or, even better, combine those two into a bitflags!?)

Fragment memory usage tends to have an outsized effect on Servo's performance, since they do get moved around a lot (more than they should, but fixing this will be a good bit of work).

/// The range within the above text run that this represents.
pub range: Range<CharIndex>,

@@ -677,13 +678,15 @@ impl ScannedTextFragmentInfo {
pub fn new(run: Arc<TextRun>,
range: Range<CharIndex>,
content_size: LogicalSize<Au>,
insertion_point: &Option<CharIndex>,
insertion_point: Option<CharIndex>,
selected: bool,
requires_line_break_afterward_if_wrapping_on_newlines: bool)
-> ScannedTextFragmentInfo {
ScannedTextFragmentInfo {
run: run,
range: range,
insertion_point: *insertion_point,
insertion_point: insertion_point,
selected: selected,
content_size: content_size,
range_end_including_stripped_whitespace: range.end(),
requires_line_break_afterward_if_wrapping_on_newlines:
@@ -737,19 +740,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,
}
}
}
@@ -872,7 +873,8 @@ impl Fragment {
text_run,
split.range,
size,
&None,
None,
false,
requires_line_break_afterward_if_wrapping_on_newlines);
self.transform(size, SpecificFragmentInfo::ScannedText(info))
}
@@ -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)
@@ -172,17 +172,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 +217,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,15 +241,18 @@ 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);
}
run_info.font_index = font_index;
run_info.bidi_level = bidi_level;
run_info.script = script;
mapping.selected = selected;
}

// Consume this character.
@@ -334,7 +348,8 @@ impl TextRunScanner {
scanned_run.run,
mapping.char_range,
text_size,
&scanned_run.insertion_point,
scanned_run.insertion_point,
mapping.selected,
requires_line_break_afterward_if_wrapping_on_newlines);

let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range);
@@ -408,7 +423,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 +448,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 +516,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 +532,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};
@@ -825,7 +826,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.
///
@@ -1049,21 +1050,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"
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.