mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Adds support for input element's maxlength attr
servo/servo#7320 servo/servo#7004
This commit is contained in:
parent
2be60be062
commit
d26c555e2a
6 changed files with 196 additions and 13 deletions
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue