aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/script/dom/htmlinputelement.rs34
-rw-r--r--components/script/dom/htmltextareaelement.rs4
-rw-r--r--components/script/dom/webidls/HTMLInputElement.webidl2
-rw-r--r--components/script/textinput.rs52
-rw-r--r--components/style/attr.rs10
-rw-r--r--tests/unit/script/textinput.rs107
6 files changed, 196 insertions, 13 deletions
diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
index 524f70e1288..554030c288a 100644
--- a/components/script/dom/htmlinputelement.rs
+++ b/components/script/dom/htmlinputelement.rs
@@ -8,8 +8,8 @@ use dom::attr::{Attr, AttrValue};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
-use dom::bindings::codegen::Bindings::HTMLInputElementBinding;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
+use dom::bindings::codegen::Bindings::HTMLInputElementBinding;
use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
@@ -32,6 +32,7 @@ use msg::constellation_msg::ScriptMsg as ConstellationMsg;
use selectors::states::*;
use std::borrow::ToOwned;
use std::cell::Cell;
+use std::i32;
use string_cache::Atom;
use textinput::KeyReaction::{DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction};
use textinput::Lines::Single;
@@ -116,7 +117,7 @@ impl HTMLInputElement {
checked_changed: Cell::new(false),
value_changed: Cell::new(false),
size: Cell::new(DEFAULT_INPUT_SIZE),
- textinput: DOMRefCell::new(TextInput::new(Single, DOMString::new(), chan)),
+ textinput: DOMRefCell::new(TextInput::new(Single, DOMString::new(), chan, None)),
activation_state: DOMRefCell::new(InputActivationState::new())
}
}
@@ -337,6 +338,21 @@ impl HTMLInputElementMethods for HTMLInputElement {
// https://html.spec.whatwg.org/multipage/#dom-input-formtarget
make_setter!(SetFormTarget, "formtarget");
+ // https://html.spec.whatwg.org/multipage/#dom-input-maxlength
+ fn MaxLength(&self) -> i32 {
+ match self.textinput.borrow().max_length {
+ Some(max_length) => max_length as i32,
+ None => i32::MAX
+ }
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-input-maxlength
+ fn SetMaxLength(&self, max_length: i32) {
+ if max_length > 0 {
+ self.textinput.borrow_mut().max_length = Some(max_length as usize)
+ }
+ }
+
// https://html.spec.whatwg.org/multipage/#dom-input-indeterminate
fn Indeterminate(&self) -> bool {
self.upcast::<Element>().get_state().contains(IN_INDETERMINATE_STATE)
@@ -511,6 +527,7 @@ impl VirtualMethods for HTMLInputElement {
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
+
match attr.local_name() {
&atom!("disabled") => {
let disabled_state = match mutation {
@@ -581,6 +598,18 @@ impl VirtualMethods for HTMLInputElement {
self.radio_group_updated(
mutation.new_value(attr).as_ref().map(|name| name.as_atom()));
},
+ &atom!("maxlength") => {
+ match *attr.value() {
+ AttrValue::Int(_, value) => {
+ if value < 0 {
+ self.textinput.borrow_mut().max_length = None
+ } else {
+ self.textinput.borrow_mut().max_length = Some(value as usize)
+ }
+ },
+ _ => panic!("Expected an AttrValue::UInt"),
+ }
+ }
&atom!("placeholder") => {
// FIXME(ajeffrey): Should we do in-place mutation of the placeholder?
let mut placeholder = self.placeholder.borrow_mut();
@@ -599,6 +628,7 @@ impl VirtualMethods for HTMLInputElement {
&atom!("name") => AttrValue::from_atomic(value),
&atom!("size") => AttrValue::from_limited_u32(value, DEFAULT_INPUT_SIZE),
&atom!("type") => AttrValue::from_atomic(value),
+ &atom!("maxlength") => AttrValue::from_i32(value, i32::MAX),
_ => self.super_type().unwrap().parse_plain_attribute(name, value),
}
}
diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs
index d4e1729f662..94057d4eee0 100644
--- a/components/script/dom/htmltextareaelement.rs
+++ b/components/script/dom/htmltextareaelement.rs
@@ -30,6 +30,7 @@ use script_task::ScriptTaskEventCategory::InputEvent;
use script_task::{CommonScriptMsg, Runnable};
use selectors::states::*;
use std::cell::Cell;
+use std::i32;
use string_cache::Atom;
use textinput::{KeyReaction, Lines, TextInput};
use util::str::DOMString;
@@ -89,6 +90,7 @@ impl<'a> RawLayoutHTMLTextAreaElementHelpers for &'a HTMLTextAreaElement {
static DEFAULT_COLS: u32 = 20;
static DEFAULT_ROWS: u32 = 2;
+static DEFAULT_MAX_LENGTH: i32 = i32::MAX;
impl HTMLTextAreaElement {
fn new_inherited(localName: DOMString,
@@ -99,7 +101,7 @@ impl HTMLTextAreaElement {
htmlelement:
HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
localName, prefix, document),
- textinput: DOMRefCell::new(TextInput::new(Lines::Multiple, DOMString::new(), chan)),
+ textinput: DOMRefCell::new(TextInput::new(Lines::Multiple, DOMString::new(), chan, None)),
cols: Cell::new(DEFAULT_COLS),
rows: Cell::new(DEFAULT_ROWS),
value_changed: Cell::new(false),
diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl
index a5472818afe..b2b7992f5e0 100644
--- a/components/script/dom/webidls/HTMLInputElement.webidl
+++ b/components/script/dom/webidls/HTMLInputElement.webidl
@@ -25,7 +25,7 @@ interface HTMLInputElement : HTMLElement {
// attribute DOMString inputMode;
//readonly attribute HTMLElement? list;
// attribute DOMString max;
- // attribute long maxLength;
+ attribute long maxLength;
// attribute DOMString min;
// attribute long minLength;
// attribute boolean multiple;
diff --git a/components/script/textinput.rs b/components/script/textinput.rs
index aa970e27464..b5bd6421127 100644
--- a/components/script/textinput.rs
+++ b/components/script/textinput.rs
@@ -11,6 +11,7 @@ use msg::constellation_msg::{Key, KeyModifiers};
use std::borrow::ToOwned;
use std::cmp::{max, min};
use std::default::Default;
+use std::usize;
use util::mem::HeapSizeOf;
use util::str::DOMString;
@@ -41,6 +42,7 @@ pub struct TextInput<T: ClipboardProvider> {
multiline: bool,
#[ignore_heap_size_of = "Can't easily measure this generic type"]
clipboard_provider: T,
+ pub max_length: Option<usize>
}
/// Resulting action to be taken by the owner of a text input that is handling an event.
@@ -107,13 +109,14 @@ fn is_printable_key(key: Key) -> bool {
impl<T: ClipboardProvider> TextInput<T> {
/// Instantiate a new text input control
- pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
+ pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T, max_length: Option<usize>) -> TextInput<T> {
let mut i = TextInput {
lines: vec!(),
edit_point: Default::default(),
selection_begin: None,
multiline: lines == Lines::Multiple,
- clipboard_provider: clipboard_provider
+ clipboard_provider: clipboard_provider,
+ max_length: max_length
};
i.set_content(initial);
i
@@ -133,7 +136,7 @@ impl<T: ClipboardProvider> TextInput<T> {
}
/// Insert a string at the current editing point
- fn insert_string<S: Into<String>>(&mut self, s: S) {
+ pub fn insert_string<S: Into<String>>(&mut self, s: S) {
if self.selection_begin.is_none() {
self.selection_begin = Some(self.edit_point);
}
@@ -170,8 +173,40 @@ impl<T: ClipboardProvider> TextInput<T> {
})
}
+ fn selection_len(&self) -> usize {
+ if let Some((begin, end)) = self.get_sorted_selection() {
+ let prefix = &self.lines[begin.line][0..begin.index];
+ let suffix = &self.lines[end.line][end.index..];
+ let lines_prefix = &self.lines[..begin.line];
+ let lines_suffix = &self.lines[end.line + 1..];
+
+ self.len() - (prefix.chars().count() +
+ suffix.chars().count() +
+ lines_prefix.iter().fold(0, |m, i| m + i.chars().count() + 1) +
+ lines_suffix.iter().fold(0, |m, i| m + i.chars().count() + 1))
+ } else {
+ 0
+ }
+ }
+
pub fn replace_selection(&mut self, insert: DOMString) {
if let Some((begin, end)) = self.get_sorted_selection() {
+ let allowed_to_insert_count = if let Some(max_length) = self.max_length {
+ let len_after_selection_replaced = self.len() - self.selection_len();
+ if len_after_selection_replaced > max_length {
+ // If, after deleting the selection, the len is still greater than the max
+ // length, then don't delete/insert anything
+ return
+ }
+
+ max_length - len_after_selection_replaced
+ } else {
+ usize::MAX
+ };
+
+ let last_char_to_insert = min(allowed_to_insert_count, insert.chars().count());
+ let chars_to_insert = (&insert[0 .. last_char_to_insert]).to_owned();
+
self.clear_selection();
let new_lines = {
@@ -181,13 +216,14 @@ impl<T: ClipboardProvider> TextInput<T> {
let lines_suffix = &self.lines[end.line + 1..];
let mut insert_lines = if self.multiline {
- insert.split('\n').map(DOMString::from).collect()
+ chars_to_insert.split('\n').map(|s| DOMString::from(s.to_owned())).collect()
} else {
- vec!(insert)
+ vec!(DOMString::from(chars_to_insert))
};
// FIXME(ajeffrey): effecient append for DOMStrings
let mut new_line = prefix.to_owned();
+
new_line.push_str(&insert_lines[0]);
insert_lines[0] = DOMString::from(new_line);
@@ -434,6 +470,12 @@ impl<T: ClipboardProvider> TextInput<T> {
}
}
+ pub fn len(&self) -> usize {
+ self.lines.iter().fold(0, |m, l| {
+ m + l.len() + 1
+ }) - 1
+ }
+
/// Get the current contents of the text input. Multiple lines are joined by \n.
pub fn get_content(&self) -> DOMString {
let mut content = "".to_owned();
diff --git a/components/style/attr.rs b/components/style/attr.rs
index 625eeee3a5c..e50071e3b3f 100644
--- a/components/style/attr.rs
+++ b/components/style/attr.rs
@@ -5,7 +5,7 @@
use cssparser::RGBA;
use std::ops::Deref;
use string_cache::{Atom, Namespace};
-use util::str::{DOMString, LengthOrPercentageOrAuto, parse_unsigned_integer, parse_legacy_color, parse_length};
+use util::str::{DOMString, LengthOrPercentageOrAuto, parse_integer, parse_unsigned_integer, parse_legacy_color, parse_length};
use util::str::{split_html_space_chars, str_join};
use values::specified::{Length};
@@ -17,6 +17,7 @@ pub enum AttrValue {
String(DOMString),
TokenList(DOMString, Vec<Atom>),
UInt(DOMString, u32),
+ Int(DOMString, i32),
Atom(Atom),
Length(DOMString, Option<Length>),
Color(DOMString, Option<RGBA>),
@@ -52,6 +53,12 @@ impl AttrValue {
AttrValue::UInt(string, result)
}
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#limited-to-only-non-negative-numbers
+ pub fn from_i32(string: DOMString, default: i32) -> AttrValue {
+ let result = parse_integer(string.chars()).unwrap_or(default);
+ AttrValue::Int(string, result)
+ }
+
// https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero
pub fn from_limited_u32(string: DOMString, default: u32) -> AttrValue {
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
@@ -165,6 +172,7 @@ impl Deref for AttrValue {
AttrValue::UInt(ref value, _) |
AttrValue::Length(ref value, _) |
AttrValue::Color(ref value, _) |
+ AttrValue::Int(ref value, _) |
AttrValue::Dimension(ref value, _) => &value,
AttrValue::Atom(ref value) => &value,
}
diff --git a/tests/unit/script/textinput.rs b/tests/unit/script/textinput.rs
index 3d62f1f32ed..25ff3e827a0 100644
--- a/tests/unit/script/textinput.rs
+++ b/tests/unit/script/textinput.rs
@@ -13,11 +13,111 @@ use msg::constellation_msg::CONTROL;
use msg::constellation_msg::SUPER;
use msg::constellation_msg::{Key, KeyModifiers};
use script::clipboard_provider::DummyClipboardContext;
-use script::textinput::{TextInput, Selection, Lines, Direction};
+use script::textinput::{TextInput, TextPoint, Selection, Lines, Direction};
use util::str::DOMString;
fn text_input(lines: Lines, s: &str) -> TextInput<DummyClipboardContext> {
- TextInput::new(lines, DOMString::from(s), DummyClipboardContext::new(""))
+ TextInput::new(lines, DOMString::from(s), DummyClipboardContext::new(""), None)
+}
+
+#[test]
+fn test_textinput_when_inserting_multiple_lines_over_a_selection_respects_max_length() {
+ let mut textinput = TextInput::new(
+ Lines::Multiple,
+ DOMString::from("hello\nworld"),
+ DummyClipboardContext::new(""),
+ Some(17)
+ );
+
+ textinput.edit_point = TextPoint { line: 0, index: 1 };
+ textinput.adjust_horizontal(3, Selection::Selected);
+ textinput.adjust_vertical(1, Selection::Selected);
+
+ // Selection is now "hello\n
+ // ------
+ // world"
+ // ----
+
+ textinput.insert_string("cruel\nterrible\nbad".to_string());
+
+ assert_eq!(textinput.get_content(), "hcruel\nterrible\nd");
+}
+
+#[test]
+fn test_textinput_when_inserting_multiple_lines_still_respects_max_length() {
+ let mut textinput = TextInput::new(
+ Lines::Multiple,
+ DOMString::from("hello\nworld"),
+ DummyClipboardContext::new(""),
+ Some(17)
+ );
+
+ textinput.edit_point = TextPoint { line: 1, index: 0 };
+
+ textinput.insert_string("cruel\nterrible".to_string());
+
+ assert_eq!(textinput.get_content(), "hello\ncruel\nworld");
+}
+
+#[test]
+fn test_textinput_when_content_is_already_longer_than_max_length_and_theres_no_selection_dont_insert_anything() {
+ let mut textinput = TextInput::new(
+ Lines::Single,
+ DOMString::from("abc"),
+ DummyClipboardContext::new(""),
+ Some(1)
+ );
+
+ textinput.insert_char('a');
+
+ assert_eq!(textinput.get_content(), "abc");
+}
+
+#[test]
+fn test_multi_line_textinput_with_maxlength_doesnt_allow_appending_characters_when_input_spans_lines() {
+ let mut textinput = TextInput::new(
+ Lines::Multiple,
+ DOMString::from("abc\nd"),
+ DummyClipboardContext::new(""),
+ Some(5)
+ );
+
+ textinput.insert_char('a');
+
+ assert_eq!(textinput.get_content(), "abc\nd");
+}
+
+#[test]
+fn test_single_line_textinput_with_max_length_doesnt_allow_appending_characters_when_replacing_a_selection() {
+ let mut textinput = TextInput::new(
+ Lines::Single,
+ DOMString::from("abcde"),
+ DummyClipboardContext::new(""),
+ Some(5)
+ );
+
+ textinput.edit_point = TextPoint { line: 0, index: 1 };
+ textinput.adjust_horizontal(3, Selection::Selected);
+
+ // Selection is now "abcde"
+ // ---
+
+ textinput.replace_selection(DOMString::from("too long"));
+
+ assert_eq!(textinput.get_content(), "atooe");
+}
+
+#[test]
+fn test_single_line_textinput_with_max_length_doesnt_allow_appending_characters_after_max_length_is_reached() {
+ let mut textinput = TextInput::new(
+ Lines::Single,
+ DOMString::from("a"),
+ DummyClipboardContext::new(""),
+ Some(1)
+ );
+
+ textinput.insert_char('b');
+ assert_eq!(textinput.get_content(), "a");
}
#[test]
@@ -199,7 +299,8 @@ fn test_clipboard_paste() {
let mut textinput = TextInput::new(Lines::Single,
DOMString::from("defg"),
- DummyClipboardContext::new("abc"));
+ DummyClipboardContext::new("abc"),
+ None);
assert_eq!(textinput.get_content(), "defg");
assert_eq!(textinput.edit_point.index, 0);
textinput.handle_keydown_aux(Key::V, MODIFIERS);