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

Adds support for input element's maxlength attr #7323

Merged
merged 14 commits into from Dec 3, 2015
@@ -1090,6 +1090,30 @@ impl Element {
self.set_attribute(local_name, AttrValue::from_atomic_tokens(tokens));
}

pub fn get_int_attribute(&self, local_name: &Atom, default: i32) -> i32 {
// TODO: Is this assert necessary?
assert!(local_name.chars().all(|ch| {
!ch.is_ascii() || ch.to_ascii_lowercase() == ch
}));
let attribute = self.get_attribute(&ns!(), local_name);

match attribute {
Some(ref attribute) => {
match *attribute.r().value() {
AttrValue::Int(_, value) => value,
_ => panic!("Expected an AttrValue::Int: \
implement parse_plain_attribute"),
}
}
None => default,
}
}

pub fn set_int_attribute(&self, local_name: &Atom, value: i32) {
assert!(&**local_name == local_name.to_ascii_lowercase());
self.set_attribute(local_name, AttrValue::Int(DOMString::from(value.to_string()), value));
}

pub fn get_uint_attribute(&self, local_name: &Atom, default: u32) -> u32 {
assert!(local_name.chars().all(|ch| !ch.is_ascii() || ch.to_ascii_lowercase() == ch));
let attribute = self.get_attribute(&ns!(), local_name);
@@ -64,6 +64,7 @@ pub struct HTMLInputElement {
placeholder: DOMRefCell<DOMString>,
value_changed: Cell<bool>,
size: Cell<u32>,
maxlength: Cell<i32>,
#[ignore_heap_size_of = "#7193"]
textinput: DOMRefCell<TextInput<ConstellationChan<ConstellationMsg>>>,
activation_state: DOMRefCell<InputActivationState>,
@@ -103,6 +104,7 @@ impl InputActivationState {
}

static DEFAULT_INPUT_SIZE: u32 = 20;
static DEFAULT_MAX_LENGTH: i32 = -1;

impl HTMLInputElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: &Document) -> HTMLInputElement {
@@ -115,8 +117,9 @@ impl HTMLInputElement {
placeholder: DOMRefCell::new(DOMString::new()),
checked_changed: Cell::new(false),
value_changed: Cell::new(false),
maxlength: Cell::new(DEFAULT_MAX_LENGTH),
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 +340,12 @@ 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
make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);

// https://html.spec.whatwg.org/multipage/#dom-input-maxlength
make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);

// https://html.spec.whatwg.org/multipage/#dom-input-indeterminate
fn Indeterminate(&self) -> bool {
self.upcast::<Element>().get_state().contains(IN_INDETERMINATE_STATE)
@@ -511,6 +520,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 +591,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::Int"),
}
}
&atom!("placeholder") => {
// FIXME(ajeffrey): Should we do in-place mutation of the placeholder?
let mut placeholder = self.placeholder.borrow_mut();
@@ -599,6 +621,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_limited_i32(value, DEFAULT_MAX_LENGTH),
_ => self.super_type().unwrap().parse_plain_attribute(name, value),
}
}
@@ -99,7 +99,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),
@@ -26,6 +26,42 @@ macro_rules! make_bool_getter(
);
);

#[macro_export]
macro_rules! make_limited_int_setter(
($attr:ident, $htmlname:tt, $default:expr) => (
fn $attr(&self, value: i32) -> $crate::dom::bindings::error::ErrorResult {
use dom::bindings::inheritance::Castable;
use dom::element::Element;

let value = if value < 0 {
return Err($crate::dom::bindings::error::Error::IndexSize);
} else {
value
};

let element = self.upcast::<Element>();
element.set_int_attribute(&atom!($htmlname), value);
Ok(())
}
);
);

#[macro_export]
macro_rules! make_int_getter(
($attr:ident, $htmlname:tt, $default:expr) => (
fn $attr(&self) -> i32 {
use dom::bindings::inheritance::Castable;
use dom::element::Element;
let element = self.upcast::<Element>();
element.get_int_attribute(&atom!($htmlname), $default)
}
);

($attr:ident, $htmlname:tt) => {
make_int_getter!($attr, $htmlname, 0);
};
);

#[macro_export]
macro_rules! make_uint_getter(
($attr:ident, $htmlname:tt, $default:expr) => (
@@ -25,7 +25,8 @@ interface HTMLInputElement : HTMLElement {
// attribute DOMString inputMode;
//readonly attribute HTMLElement? list;
// attribute DOMString max;
// attribute long maxLength;
[SetterThrows]
attribute long maxLength;
// attribute DOMString min;
// attribute long minLength;
// attribute boolean multiple;
@@ -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();
@@ -6,7 +6,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::{split_html_space_chars, str_join};
use util::str::{split_html_space_chars, str_join, parse_integer};
use values::specified::{Length};

// Duplicated from script::dom::values.
@@ -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,22 @@ impl AttrValue {
AttrValue::UInt(string, result)
}

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
pub fn from_limited_i32(string: DOMString, default: i32) -> AttrValue {
let result = parse_integer(string.chars()).unwrap_or(default);

if result < 0 {
AttrValue::Int(string, default)
} else {
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 +182,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,
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.