diff options
author | Josh Matthews <josh@joshmatthews.net> | 2014-10-06 11:51:44 -0400 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2014-11-13 12:53:54 -0500 |
commit | 80764f65e3e4895d3a012d6bc1d3bb8183ae15da (patch) | |
tree | 63438517484e439fbc62693a5e53a1029f5206ba /components/script | |
parent | 84bc17e7ad99d11ff416f2126acb2031b680dc19 (diff) | |
download | servo-80764f65e3e4895d3a012d6bc1d3bb8183ae15da.tar.gz servo-80764f65e3e4895d3a012d6bc1d3bb8183ae15da.zip |
Add single-line text input with no visible cursor.
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/element.rs | 26 | ||||
-rw-r--r-- | components/script/dom/htmlinputelement.rs | 77 | ||||
-rw-r--r-- | components/script/dom/keyboardevent.rs | 433 | ||||
-rw-r--r-- | components/script/lib.rs | 1 | ||||
-rw-r--r-- | components/script/script_task.rs | 26 | ||||
-rw-r--r-- | components/script/textinput.rs | 259 |
6 files changed, 767 insertions, 55 deletions
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 9c2031c79cf..87d3f889544 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -26,7 +26,7 @@ use dom::document::{Document, DocumentHelpers, LayoutDocumentHelpers}; use dom::domtokenlist::DOMTokenList; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlcollection::HTMLCollection; -use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; +use dom::htmlinputelement::{HTMLInputElement, RawLayoutHTMLInputElementHelpers}; use dom::htmlserializer::serialize; use dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementHelpers}; use dom::node::{ElementNodeTypeId, Node, NodeHelpers, NodeIterator, document_from_node}; @@ -231,17 +231,24 @@ pub trait RawLayoutElementHelpers { -> Option<i32>; } +#[inline] +#[allow(unrooted_must_root)] +unsafe fn get_attr_for_layout<'a>(elem: &'a Element, namespace: &Namespace, name: &Atom) -> Option<&'a JS<Attr>> { + // cast to point to T in RefCell<T> directly + let attrs: *const Vec<JS<Attr>> = mem::transmute(&elem.attrs); + (*attrs).iter().find(|attr: & &JS<Attr>| { + let attr = attr.unsafe_get(); + *name == (*attr).local_name_atom_forever() && + (*attr).namespace() == namespace + }) +} + impl RawLayoutElementHelpers for Element { #[inline] #[allow(unrooted_must_root)] unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a str> { - let attrs = self.attrs.borrow_for_layout(); - (*attrs).iter().find(|attr: & &JS<Attr>| { - let attr = attr.unsafe_get(); - *name == (*attr).local_name_atom_forever() && - (*attr).namespace() == namespace - }).map(|attr| { + get_attr_for_layout(self, namespace, name).map(|attr| { let attr = attr.unsafe_get(); (*attr).value_ref_forever() }) @@ -337,6 +344,7 @@ impl RawLayoutElementHelpers for Element { pub trait LayoutElementHelpers { unsafe fn html_element_in_html_document_for_layout(&self) -> bool; + unsafe fn has_attr_for_layout(&self, namespace: &Namespace, name: &Atom) -> bool; } impl LayoutElementHelpers for JS<Element> { @@ -349,6 +357,10 @@ impl LayoutElementHelpers for JS<Element> { let node: JS<Node> = self.transmute_copy(); node.owner_doc_for_layout().is_html_document_for_layout() } + + unsafe fn has_attr_for_layout(&self, namespace: &Namespace, name: &Atom) -> bool { + get_attr_for_layout(&*self.unsafe_get(), namespace, name).is_some() + } } pub trait ElementHelpers<'a> { diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 44e8b21ab18..547c0f57aa2 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -13,16 +13,20 @@ use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementM use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLFormElementCast, HTMLInputElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLFieldSetElementDerived}; +use dom::bindings::codegen::InheritTypes::KeyboardEventCast; use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, ResultRootable}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::{Document, DocumentHelpers}; -use dom::element::{AttributeHandlers, Element, HTMLInputElementTypeId}; +use dom::element::{AttributeHandlers, Element, HTMLInputElementTypeId, LayoutElementHelpers}; +use dom::element::RawLayoutElementHelpers; use dom::event::Event; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; +use dom::keyboardevent::KeyboardEvent; use dom::htmlformelement::{InputElement, FormOwner, HTMLFormElement, HTMLFormElementHelpers, NotFromFormSubmitMethod}; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, document_from_node, window_from_node}; use dom::virtualmethods::VirtualMethods; +use textinput::{TextInput, TriggerDefaultAction, DispatchInput, Nothing}; use servo_util::str::DOMString; use string_cache::Atom; @@ -51,9 +55,8 @@ pub struct HTMLInputElement { htmlelement: HTMLElement, input_type: Cell<InputType>, checked: Cell<bool>, - uncommitted_value: DOMRefCell<Option<String>>, - value: DOMRefCell<Option<String>>, size: Cell<u32>, + textinput: DOMRefCell<TextInput>, } impl HTMLInputElementDerived for EventTarget { @@ -70,9 +73,8 @@ impl HTMLInputElement { htmlelement: HTMLElement::new_inherited(HTMLInputElementTypeId, localName, prefix, document), input_type: Cell::new(InputText), checked: Cell::new(false), - uncommitted_value: DOMRefCell::new(None), - value: DOMRefCell::new(None), size: Cell::new(DEFAULT_INPUT_SIZE), + textinput: DOMRefCell::new(TextInput::new(false, "".to_string())), } } @@ -84,40 +86,55 @@ impl HTMLInputElement { } pub trait LayoutHTMLInputElementHelpers { - unsafe fn get_value_for_layout(&self) -> String; + unsafe fn get_value_for_layout(self) -> String; + unsafe fn get_size_for_layout(self) -> u32; +} + +pub trait RawLayoutHTMLInputElementHelpers { unsafe fn get_size_for_layout(&self) -> u32; } -impl LayoutHTMLInputElementHelpers for HTMLInputElement { +impl LayoutHTMLInputElementHelpers for JS<HTMLInputElement> { #[allow(unrooted_must_root)] - unsafe fn get_value_for_layout(&self) -> String { - match self.input_type.get() { + unsafe fn get_value_for_layout(self) -> String { + unsafe fn get_raw_textinput_value(input: JS<HTMLInputElement>) -> Option<String> { + let elem: JS<Element> = input.transmute_copy(); + if !elem.has_attr_for_layout(&ns!(""), &atom!("value")) { + return None; + } + Some((*input.unsafe_get()).textinput.borrow_for_layout().get_content()) + } + + unsafe fn get_raw_attr_value(input: JS<HTMLInputElement>) -> Option<String> { + let elem: JS<Element> = input.transmute_copy(); + (*elem.unsafe_get()).get_attr_val_for_layout(&ns!(""), &atom!("value")) + .map(|s| s.to_string()) + } + + match (*self.unsafe_get()).input_type.get() { InputCheckbox | InputRadio => "".to_string(), InputFile | InputImage => "".to_string(), - InputButton(ref default) => self.value.borrow_for_layout().clone() + InputButton(ref default) => get_raw_attr_value(self) .or_else(|| default.map(|v| v.to_string())) .unwrap_or_else(|| "".to_string()), InputPassword => { - let raw = self.value.borrow_for_layout().clone().unwrap_or_else(|| "".to_string()); - String::from_char(raw.len(), '*') + let raw = get_raw_textinput_value(self).unwrap_or_else(|| "".to_string()); + String::from_char(raw.len(), '●') } - _ => self.value.borrow_for_layout().clone().unwrap_or_else(|| "".to_string()), + _ => get_raw_textinput_value(self).unwrap_or_else(|| "".to_string()), } } #[allow(unrooted_must_root)] - unsafe fn get_size_for_layout(&self) -> u32 { - self.size.get() + unsafe fn get_size_for_layout(self) -> u32 { + (*self.unsafe_get()).get_size_for_layout() } } -impl LayoutHTMLInputElementHelpers for JS<HTMLInputElement> { - unsafe fn get_value_for_layout(&self) -> String { - (*self.unsafe_get()).get_value_for_layout() - } - +impl RawLayoutHTMLInputElementHelpers for HTMLInputElement { + #[allow(unrooted_must_root)] unsafe fn get_size_for_layout(&self) -> u32 { - (*self.unsafe_get()).get_size_for_layout() + self.size.get() } } @@ -156,7 +173,7 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> { // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value fn Value(self) -> DOMString { - self.value.borrow().clone().unwrap_or("".to_string()) + self.textinput.borrow().get_content() } // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value @@ -309,7 +326,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { self.force_relayout(); } &atom!("value") => { - *self.value.borrow_mut() = Some(attr.value().as_slice().to_string()); + self.textinput.borrow_mut().set_content(attr.value().as_slice().to_string()); self.force_relayout(); } &atom!("name") => { @@ -353,7 +370,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { self.force_relayout(); } &atom!("value") => { - *self.value.borrow_mut() = None; + self.textinput.borrow_mut().set_content("".to_string()); self.force_relayout(); } &atom!("name") => { @@ -418,6 +435,18 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { let doc = document_from_node(*self).root(); doc.request_focus(ElementCast::from_ref(*self)); + } else if "keydown" == event.Type().as_slice() && !event.DefaultPrevented() && + (self.input_type.get() == InputText|| self.input_type.get() == InputPassword) { + let keyevent: Option<JSRef<KeyboardEvent>> = KeyboardEventCast::to_ref(event); + keyevent.map(|event| { + match self.textinput.borrow_mut().handle_keydown(event) { + TriggerDefaultAction => (), + DispatchInput => { + self.force_relayout(); + } + Nothing => (), + } + }); } } } diff --git a/components/script/dom/keyboardevent.rs b/components/script/dom/keyboardevent.rs index 2c8f5e49a10..a6b488d947e 100644 --- a/components/script/dom/keyboardevent.rs +++ b/components/script/dom/keyboardevent.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::KeyboardEventBinding; -use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; +use dom::bindings::codegen::Bindings::KeyboardEventBinding::{KeyboardEventMethods, KeyboardEventConstants}; use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; use dom::bindings::codegen::InheritTypes::{UIEventCast, KeyboardEventDerived}; use dom::bindings::error::Fallible; @@ -111,22 +111,431 @@ impl KeyboardEvent { Ok(event) } - pub fn key_properties(key: constellation_msg::Key) -> KeyEventProperties { - match key { - _ => KeyEventProperties { - key: "".to_string(), - code: "".to_string(), - location: 0, - char_code: None, - key_code: 0, + pub fn key_properties(key: constellation_msg::Key, mods: constellation_msg::KeyModifiers) + -> KeyEventProperties { + KeyEventProperties { + key: key_value(key, mods), + code: code_value(key), + location: key_location(key), + char_code: key_char_code(key, mods), + key_code: key_key_code(key), } - } + } +} + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-key.html +fn key_value(key: constellation_msg::Key, mods: constellation_msg::KeyModifiers) -> &'static str { + let shift = mods.contains(constellation_msg::SHIFT); + match key { + constellation_msg::KeySpace => " ", + constellation_msg::KeyApostrophe if shift => "\"", + constellation_msg::KeyApostrophe => "'", + constellation_msg::KeyComma if shift => "<", + constellation_msg::KeyComma => ",", + constellation_msg::KeyMinus if shift => "_", + constellation_msg::KeyMinus => "-", + constellation_msg::KeyPeriod if shift => ">", + constellation_msg::KeyPeriod => ".", + constellation_msg::KeySlash if shift => "?", + constellation_msg::KeySlash => "/", + constellation_msg::Key0 if shift => ")", + constellation_msg::Key0 => "0", + constellation_msg::Key1 if shift => "!", + constellation_msg::Key1 => "1", + constellation_msg::Key2 if shift => "@", + constellation_msg::Key2 => "2", + constellation_msg::Key3 if shift => "#", + constellation_msg::Key3 => "3", + constellation_msg::Key4 if shift => "$", + constellation_msg::Key4 => "4", + constellation_msg::Key5 if shift => "%", + constellation_msg::Key5 => "5", + constellation_msg::Key6 if shift => "^", + constellation_msg::Key6 => "6", + constellation_msg::Key7 if shift => "&", + constellation_msg::Key7 => "7", + constellation_msg::Key8 if shift => "*", + constellation_msg::Key8 => "8", + constellation_msg::Key9 if shift => "(", + constellation_msg::Key9 => "9", + constellation_msg::KeySemicolon if shift => ":", + constellation_msg::KeySemicolon => ";", + constellation_msg::KeyEqual if shift => "+", + constellation_msg::KeyEqual => "=", + constellation_msg::KeyA if shift => "A", + constellation_msg::KeyA => "a", + constellation_msg::KeyB if shift => "B", + constellation_msg::KeyB => "b", + constellation_msg::KeyC if shift => "C", + constellation_msg::KeyC => "c", + constellation_msg::KeyD if shift => "D", + constellation_msg::KeyD => "d", + constellation_msg::KeyE if shift => "E", + constellation_msg::KeyE => "e", + constellation_msg::KeyF if shift => "F", + constellation_msg::KeyF => "f", + constellation_msg::KeyG if shift => "G", + constellation_msg::KeyG => "g", + constellation_msg::KeyH if shift => "H", + constellation_msg::KeyH => "h", + constellation_msg::KeyI if shift => "I", + constellation_msg::KeyI => "i", + constellation_msg::KeyJ if shift => "J", + constellation_msg::KeyJ => "j", + constellation_msg::KeyK if shift => "K", + constellation_msg::KeyK => "k", + constellation_msg::KeyL if shift => "L", + constellation_msg::KeyL => "l", + constellation_msg::KeyM if shift => "M", + constellation_msg::KeyM => "m", + constellation_msg::KeyN if shift => "N", + constellation_msg::KeyN => "n", + constellation_msg::KeyO if shift => "O", + constellation_msg::KeyO => "o", + constellation_msg::KeyP if shift => "P", + constellation_msg::KeyP => "p", + constellation_msg::KeyQ if shift => "Q", + constellation_msg::KeyQ => "q", + constellation_msg::KeyR if shift => "R", + constellation_msg::KeyR => "r", + constellation_msg::KeyS if shift => "S", + constellation_msg::KeyS => "s", + constellation_msg::KeyT if shift => "T", + constellation_msg::KeyT => "t", + constellation_msg::KeyU if shift => "U", + constellation_msg::KeyU => "u", + constellation_msg::KeyV if shift => "V", + constellation_msg::KeyV => "v", + constellation_msg::KeyW if shift => "W", + constellation_msg::KeyW => "w", + constellation_msg::KeyX if shift => "X", + constellation_msg::KeyX => "x", + constellation_msg::KeyY if shift => "Y", + constellation_msg::KeyY => "y", + constellation_msg::KeyZ if shift => "Z", + constellation_msg::KeyZ => "z", + constellation_msg::KeyLeftBracket if shift => "{", + constellation_msg::KeyLeftBracket => "{", + constellation_msg::KeyBackslash if shift => "|", + constellation_msg::KeyBackslash => "\\", + constellation_msg::KeyRightBracket if shift => "}", + constellation_msg::KeyRightBracket => "]", + constellation_msg::KeyGraveAccent => "Dead", + constellation_msg::KeyWorld1 => "Unidentified", + constellation_msg::KeyWorld2 => "Unidentified", + constellation_msg::KeyEscape => "Escape", + constellation_msg::KeyEnter => "Enter", + constellation_msg::KeyTab => "Tab", + constellation_msg::KeyBackspace => "Backspace", + constellation_msg::KeyInsert => "Insert", + constellation_msg::KeyDelete => "Delete", + constellation_msg::KeyRight => "ArrowRight", + constellation_msg::KeyLeft => "ArrowLeft", + constellation_msg::KeyDown => "ArrowDown", + constellation_msg::KeyUp => "ArrowUp", + constellation_msg::KeyPageUp => "PageUp", + constellation_msg::KeyPageDown => "PageDown", + constellation_msg::KeyHome => "Home", + constellation_msg::KeyEnd => "End", + constellation_msg::KeyCapsLock => "CapsLock", + constellation_msg::KeyScrollLock => "ScrollLock", + constellation_msg::KeyNumLock => "NumLock", + constellation_msg::KeyPrintScreen => "PrintScreen", + constellation_msg::KeyPause => "Pause", + constellation_msg::KeyF1 => "F1", + constellation_msg::KeyF2 => "F2", + constellation_msg::KeyF3 => "F3", + constellation_msg::KeyF4 => "F4", + constellation_msg::KeyF5 => "F5", + constellation_msg::KeyF6 => "F6", + constellation_msg::KeyF7 => "F7", + constellation_msg::KeyF8 => "F8", + constellation_msg::KeyF9 => "F9", + constellation_msg::KeyF10 => "F10", + constellation_msg::KeyF11 => "F11", + constellation_msg::KeyF12 => "F12", + constellation_msg::KeyF13 => "F13", + constellation_msg::KeyF14 => "F14", + constellation_msg::KeyF15 => "F15", + constellation_msg::KeyF16 => "F16", + constellation_msg::KeyF17 => "F17", + constellation_msg::KeyF18 => "F18", + constellation_msg::KeyF19 => "F19", + constellation_msg::KeyF20 => "F20", + constellation_msg::KeyF21 => "F21", + constellation_msg::KeyF22 => "F22", + constellation_msg::KeyF23 => "F23", + constellation_msg::KeyF24 => "F24", + constellation_msg::KeyF25 => "F25", + constellation_msg::KeyKp0 => "0", + constellation_msg::KeyKp1 => "1", + constellation_msg::KeyKp2 => "2", + constellation_msg::KeyKp3 => "3", + constellation_msg::KeyKp4 => "4", + constellation_msg::KeyKp5 => "5", + constellation_msg::KeyKp6 => "6", + constellation_msg::KeyKp7 => "7", + constellation_msg::KeyKp8 => "8", + constellation_msg::KeyKp9 => "9", + constellation_msg::KeyKpDecimal => ".", + constellation_msg::KeyKpDivide => "/", + constellation_msg::KeyKpMultiply => "*", + constellation_msg::KeyKpSubtract => "-", + constellation_msg::KeyKpAdd => "+", + constellation_msg::KeyKpEnter => "Enter", + constellation_msg::KeyKpEqual => "=", + constellation_msg::KeyLeftShift => "Shift", + constellation_msg::KeyLeftControl => "Control", + constellation_msg::KeyLeftAlt => "Alt", + constellation_msg::KeyLeftSuper => "Super", + constellation_msg::KeyRightShift => "Shift", + constellation_msg::KeyRightControl => "Control", + constellation_msg::KeyRightAlt => "Alt", + constellation_msg::KeyRightSuper => "Super", + constellation_msg::KeyMenu => "ContextMenu", + } +} + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-code.html +fn code_value(key: constellation_msg::Key) -> &'static str { + match key { + constellation_msg::KeySpace => "Space", + constellation_msg::KeyApostrophe => "Quote", + constellation_msg::KeyComma => "Comma", + constellation_msg::KeyMinus => "Minus", + constellation_msg::KeyPeriod => "Period", + constellation_msg::KeySlash => "Slash", + constellation_msg::Key0 => "Digit0", + constellation_msg::Key1 => "Digit1", + constellation_msg::Key2 => "Digit2", + constellation_msg::Key3 => "Digit3", + constellation_msg::Key4 => "Digit4", + constellation_msg::Key5 => "Digit5", + constellation_msg::Key6 => "Digit6", + constellation_msg::Key7 => "Digit7", + constellation_msg::Key8 => "Digit8", + constellation_msg::Key9 => "Digit9", + constellation_msg::KeySemicolon => "Semicolon", + constellation_msg::KeyEqual => "Equals", + constellation_msg::KeyA => "KeyA", + constellation_msg::KeyB => "KeyB", + constellation_msg::KeyC => "KeyC", + constellation_msg::KeyD => "KeyD", + constellation_msg::KeyE => "KeyE", + constellation_msg::KeyF => "KeyF", + constellation_msg::KeyG => "KeyG", + constellation_msg::KeyH => "KeyH", + constellation_msg::KeyI => "KeyI", + constellation_msg::KeyJ => "KeyJ", + constellation_msg::KeyK => "KeyK", + constellation_msg::KeyL => "KeyL", + constellation_msg::KeyM => "KeyM", + constellation_msg::KeyN => "KeyN", + constellation_msg::KeyO => "KeyO", + constellation_msg::KeyP => "KeyP", + constellation_msg::KeyQ => "KeyQ", + constellation_msg::KeyR => "KeyR", + constellation_msg::KeyS => "KeyS", + constellation_msg::KeyT => "KeyT", + constellation_msg::KeyU => "KeyU", + constellation_msg::KeyV => "KeyV", + constellation_msg::KeyW => "KeyW", + constellation_msg::KeyX => "KeyX", + constellation_msg::KeyY => "KeyY", + constellation_msg::KeyZ => "KeyZ", + constellation_msg::KeyLeftBracket => "BracketLeft", + constellation_msg::KeyBackslash => "Backslash", + constellation_msg::KeyRightBracket => "BracketRight", + + constellation_msg::KeyGraveAccent | + constellation_msg::KeyWorld1 | + constellation_msg::KeyWorld2 => panic!("unknown char code for {}", key), + + constellation_msg::KeyEscape => "Escape", + constellation_msg::KeyEnter => "Enter", + constellation_msg::KeyTab => "Tab", + constellation_msg::KeyBackspace => "Backspace", + constellation_msg::KeyInsert => "Insert", + constellation_msg::KeyDelete => "Delete", + constellation_msg::KeyRight => "ArrowRight", + constellation_msg::KeyLeft => "ArrowLeft", + constellation_msg::KeyDown => "ArrowDown", + constellation_msg::KeyUp => "ArrowUp", + constellation_msg::KeyPageUp => "PageUp", + constellation_msg::KeyPageDown => "PageDown", + constellation_msg::KeyHome => "Home", + constellation_msg::KeyEnd => "End", + constellation_msg::KeyCapsLock => "CapsLock", + constellation_msg::KeyScrollLock => "ScrollLock", + constellation_msg::KeyNumLock => "NumLock", + constellation_msg::KeyPrintScreen => "PrintScreen", + constellation_msg::KeyPause => "Pause", + constellation_msg::KeyF1 => "F1", + constellation_msg::KeyF2 => "F2", + constellation_msg::KeyF3 => "F3", + constellation_msg::KeyF4 => "F4", + constellation_msg::KeyF5 => "F5", + constellation_msg::KeyF6 => "F6", + constellation_msg::KeyF7 => "F7", + constellation_msg::KeyF8 => "F8", + constellation_msg::KeyF9 => "F9", + constellation_msg::KeyF10 => "F10", + constellation_msg::KeyF11 => "F11", + constellation_msg::KeyF12 => "F12", + constellation_msg::KeyF13 => "F13", + constellation_msg::KeyF14 => "F14", + constellation_msg::KeyF15 => "F15", + constellation_msg::KeyF16 => "F16", + constellation_msg::KeyF17 => "F17", + constellation_msg::KeyF18 => "F18", + constellation_msg::KeyF19 => "F19", + constellation_msg::KeyF20 => "F20", + constellation_msg::KeyF21 => "F21", + constellation_msg::KeyF22 => "F22", + constellation_msg::KeyF23 => "F23", + constellation_msg::KeyF24 => "F24", + constellation_msg::KeyF25 => "F25", + constellation_msg::KeyKp0 => "Numpad0", + constellation_msg::KeyKp1 => "Numpad1", + constellation_msg::KeyKp2 => "Numpad2", + constellation_msg::KeyKp3 => "Numpad3", + constellation_msg::KeyKp4 => "Numpad4", + constellation_msg::KeyKp5 => "Numpad5", + constellation_msg::KeyKp6 => "Numpad6", + constellation_msg::KeyKp7 => "Numpad7", + constellation_msg::KeyKp8 => "Numpad8", + constellation_msg::KeyKp9 => "Numpad9", + constellation_msg::KeyKpDecimal => "NumpadDecimal", + constellation_msg::KeyKpDivide => "NumpadDivide", + constellation_msg::KeyKpMultiply => "NumpadMultiply", + constellation_msg::KeyKpSubtract => "NumpadSubtract", + constellation_msg::KeyKpAdd => "NumpadAdd", + constellation_msg::KeyKpEnter => "NumpadEnter", + constellation_msg::KeyKpEqual => "NumpadEquals", + constellation_msg::KeyLeftShift | constellation_msg::KeyRightShift => "Shift", + constellation_msg::KeyLeftControl | constellation_msg::KeyRightControl => "Control", + constellation_msg::KeyLeftAlt | constellation_msg::KeyRightAlt => "Alt", + constellation_msg::KeyLeftSuper | constellation_msg::KeyRightSuper => "Super", + constellation_msg::KeyMenu => "Menu", + } +} + +fn key_location(key: constellation_msg::Key) -> u32 { + match key { + constellation_msg::KeyKp0 | constellation_msg::KeyKp1 | constellation_msg::KeyKp2 | + constellation_msg::KeyKp3 | constellation_msg::KeyKp4 | constellation_msg::KeyKp5 | + constellation_msg::KeyKp6 | constellation_msg::KeyKp7 | constellation_msg::KeyKp8 | + constellation_msg::KeyKp9 | constellation_msg::KeyKpDecimal | + constellation_msg::KeyKpDivide | constellation_msg::KeyKpMultiply | + constellation_msg::KeyKpSubtract | constellation_msg::KeyKpAdd | + constellation_msg::KeyKpEnter | constellation_msg::KeyKpEqual => + KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD, + + constellation_msg::KeyLeftShift | constellation_msg::KeyLeftAlt | + constellation_msg::KeyLeftControl | constellation_msg::KeyLeftSuper => + KeyboardEventConstants::DOM_KEY_LOCATION_LEFT, + + constellation_msg::KeyRightShift | constellation_msg::KeyRightAlt | + constellation_msg::KeyRightControl | constellation_msg::KeyRightSuper => + KeyboardEventConstants::DOM_KEY_LOCATION_RIGHT, + + _ => KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD, + } +} + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#widl-KeyboardEvent-charCode +fn key_char_code(key: constellation_msg::Key, mods: constellation_msg::KeyModifiers) -> Option<u32> { + let key = key_value(key, mods); + if key.len() == 1 { + Some(key.char_at(0) as u32) + } else { + None + } +} + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#legacy-key-models +fn key_key_code(key: constellation_msg::Key) -> u32 { + match key { + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#legacy-key-models + constellation_msg::KeyBackspace => 8, + constellation_msg::KeyTab => 9, + constellation_msg::KeyEnter => 13, + constellation_msg::KeyLeftShift | constellation_msg::KeyRightShift => 16, + constellation_msg::KeyLeftControl | constellation_msg::KeyRightControl => 17, + constellation_msg::KeyLeftAlt | constellation_msg::KeyRightAlt => 18, + constellation_msg::KeyCapsLock => 20, + constellation_msg::KeyEscape => 27, + constellation_msg::KeySpace => 32, + constellation_msg::KeyPageUp => 33, + constellation_msg::KeyPageDown => 34, + constellation_msg::KeyEnd => 35, + constellation_msg::KeyHome => 36, + constellation_msg::KeyLeft => 37, + constellation_msg::KeyUp => 38, + constellation_msg::KeyRight => 39, + constellation_msg::KeyDown => 40, + constellation_msg::KeyDelete => 46, + + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#optionally-fixed-virtual-key-codes + constellation_msg::KeySemicolon => 186, + constellation_msg::KeyEqual => 187, + constellation_msg::KeyComma => 188, + constellation_msg::KeyMinus => 189, + constellation_msg::KeyPeriod => 190, + constellation_msg::KeySlash => 191, + constellation_msg::KeyLeftBracket => 219, + constellation_msg::KeyBackslash => 220, + constellation_msg::KeyRightBracket => 221, + constellation_msg::KeyApostrophe => 222, + + //§ B.2.1.3 + constellation_msg::Key0 | + constellation_msg::Key1 | + constellation_msg::Key2 | + constellation_msg::Key3 | + constellation_msg::Key4 | + constellation_msg::Key5 | + constellation_msg::Key6 | + constellation_msg::Key7 | + constellation_msg::Key8 | + constellation_msg::Key9 => key as u32 - constellation_msg::Key0 as u32 + '0' as u32, + + //§ B.2.1.4 + constellation_msg::KeyA | + constellation_msg::KeyB | + constellation_msg::KeyC | + constellation_msg::KeyD | + constellation_msg::KeyE | + constellation_msg::KeyF | + constellation_msg::KeyG | + constellation_msg::KeyH | + constellation_msg::KeyI | + constellation_msg::KeyJ | + constellation_msg::KeyK | + constellation_msg::KeyL | + constellation_msg::KeyM | + constellation_msg::KeyN | + constellation_msg::KeyO | + constellation_msg::KeyP | + constellation_msg::KeyQ | + constellation_msg::KeyR | + constellation_msg::KeyS | + constellation_msg::KeyT | + constellation_msg::KeyU | + constellation_msg::KeyV | + constellation_msg::KeyW | + constellation_msg::KeyX | + constellation_msg::KeyY | + constellation_msg::KeyZ => key as u32 - constellation_msg::KeyA as u32 + 'A' as u32, + + //§ B.2.1.8 + _ => 0 } } pub struct KeyEventProperties { - pub key: DOMString, - pub code: DOMString, + pub key: &'static str, + pub code: &'static str, pub location: u32, pub char_code: Option<u32>, pub key_code: u32, diff --git a/components/script/lib.rs b/components/script/lib.rs index 959c245b63f..123417e9429 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -221,3 +221,4 @@ pub mod layout_interface; pub mod page; pub mod script_task; mod timers; +pub mod textinput; diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 684ae327ad2..5ed9023b5e5 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -49,7 +49,7 @@ use servo_msg::compositor_msg::{FinishedLoading, LayerId, Loading}; use servo_msg::compositor_msg::{ScriptListener}; use servo_msg::constellation_msg::{ConstellationChan, LoadCompleteMsg, LoadUrlMsg, NavigationDirection}; use servo_msg::constellation_msg::{LoadData, PipelineId, Failure, FailureMsg, WindowSizeData, Key, KeyState}; -use servo_msg::constellation_msg::{KeyModifiers, Super, Shift, Control, Alt, Repeated, Pressed}; +use servo_msg::constellation_msg::{KeyModifiers, SUPER, SHIFT, CONTROL, ALT, Repeated, Pressed}; use servo_msg::constellation_msg::{Released}; use servo_msg::constellation_msg; use servo_net::image_cache_task::ImageCacheTask; @@ -923,7 +923,6 @@ impl ScriptTask { state: KeyState, modifiers: KeyModifiers, pipeline_id: PipelineId) { - println!("key {} is {}", key as int, state as int); let page = get_page(&*self.page.borrow(), pipeline_id); let frame = page.frame(); let window = frame.as_ref().unwrap().window.root(); @@ -937,10 +936,10 @@ impl ScriptTask { (&None, &None) => EventTargetCast::from_ref(*window), }; - let ctrl = modifiers.contains(Control); - let alt = modifiers.contains(Alt); - let shift = modifiers.contains(Shift); - let meta = modifiers.contains(Super); + let ctrl = modifiers.contains(CONTROL); + let alt = modifiers.contains(ALT); + let shift = modifiers.contains(SHIFT); + let meta = modifiers.contains(SUPER); let is_composing = false; let is_repeating = state == Repeated; @@ -949,21 +948,24 @@ impl ScriptTask { Released => "keyup", }.to_string(); - let props = KeyboardEvent::key_properties(key); + let props = KeyboardEvent::key_properties(key, modifiers); let event = KeyboardEvent::new(*window, ev_type, true, true, Some(*window), 0, - props.key.clone(), props.code.clone(), props.location, + props.key.to_string(), props.code.to_string(), props.location, is_repeating, is_composing, ctrl, alt, shift, meta, - props.char_code, props.key_code).root(); + None, props.key_code).root(); let _ = target.DispatchEvent(EventCast::from_ref(*event)); if state != Released && props.is_printable() { let event = KeyboardEvent::new(*window, "keypress".to_string(), true, true, Some(*window), - 0, props.key.clone(), props.code.clone(), props.location, - is_repeating, is_composing, ctrl, alt, shift, meta, - props.char_code, props.key_code).root(); + 0, props.key.to_string(), props.code.to_string(), + props.location, is_repeating, is_composing, + ctrl, alt, shift, meta, + props.char_code, 0).root(); let _ = target.DispatchEvent(EventCast::from_ref(*event)); } + + window.flush_layout(); } /// The entry point for content to notify that a new load has been requested diff --git a/components/script/textinput.rs b/components/script/textinput.rs new file mode 100644 index 00000000000..0a570e15810 --- /dev/null +++ b/components/script/textinput.rs @@ -0,0 +1,259 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Common handling of keyboard input and state management for text input controls + +use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; +use dom::bindings::js::JSRef; +use dom::keyboardevent::KeyboardEvent; +use servo_util::str::DOMString; + +use std::cmp::{min, max}; +use std::default::Default; + +#[jstraceable] +struct TextPoint { + line: uint, + index: uint, +} + +#[jstraceable] +pub struct TextInput { + /// Current text input content, split across lines without trailing '\n' + lines: Vec<DOMString>, + /// Current cursor input point + edit_point: TextPoint, + /// Selection range, beginning and end point that can span multiple lines. + _selection: Option<(TextPoint, TextPoint)>, + /// Is this ia multiline input? + multiline: bool, +} + +pub enum KeyReaction { + TriggerDefaultAction, + DispatchInput, + Nothing, +} + +impl Default for TextPoint { + fn default() -> TextPoint { + TextPoint { + line: 0, + index: 0, + } + } +} + +impl TextInput { + pub fn new(multiline: bool, initial: DOMString) -> TextInput { + let mut i = TextInput { + lines: vec!(), + edit_point: Default::default(), + _selection: None, + multiline: multiline, + }; + i.set_content(initial); + i + } + + fn get_current_line(&self) -> &DOMString { + &self.lines[self.edit_point.line] + } + + fn insert_char(&mut self, ch: char) { + //TODO: handle replacing selection with character + let new_line = { + let prefix = self.get_current_line().as_slice().slice_chars(0, self.edit_point.index); + let suffix = self.get_current_line().as_slice().slice_chars(self.edit_point.index, + self.current_line_length()); + let mut new_line = prefix.to_string(); + new_line.push(ch); + new_line.push_str(suffix.as_slice()); + new_line + }; + + self.lines[self.edit_point.line] = new_line; + self.edit_point.index += 1; + } + + fn delete_char(&mut self, forward: bool) { + //TODO: handle deleting selection + let prefix_end = if forward { + self.edit_point.index + } else { + //TODO: handle backspacing from position 0 of current line + if self.multiline { + assert!(self.edit_point.index > 0); + } else if self.edit_point.index == 0 { + return; + } + self.edit_point.index - 1 + }; + let suffix_start = if forward { + let is_eol = self.edit_point.index == self.current_line_length() - 1; + if self.multiline { + //TODO: handle deleting from end position of current line + assert!(!is_eol); + } else if is_eol { + return; + } + self.edit_point.index + 1 + } else { + self.edit_point.index + }; + + let new_line = { + let prefix = self.get_current_line().as_slice().slice_chars(0, prefix_end); + let suffix = self.get_current_line().as_slice().slice_chars(suffix_start, + self.current_line_length()); + let mut new_line = prefix.to_string(); + new_line.push_str(suffix); + new_line + }; + + self.lines[self.edit_point.line] = new_line; + + if !forward { + self.adjust_horizontal(-1); + } + } + + fn current_line_length(&self) -> uint { + self.lines[self.edit_point.line].len() + } + + fn adjust_vertical(&mut self, adjust: int) { + if !self.multiline { + return; + } + + if adjust < 0 && self.edit_point.line as int + adjust < 0 { + self.edit_point.index = 0; + self.edit_point.line = 0; + return; + } else if adjust > 0 && self.edit_point.line >= min(0, self.lines.len() - adjust as uint) { + self.edit_point.index = self.current_line_length(); + self.edit_point.line = self.lines.len() - 1; + return; + } + + self.edit_point.line = (self.edit_point.line as int + adjust) as uint; + self.edit_point.index = min(self.current_line_length(), self.edit_point.index); + } + + fn adjust_horizontal(&mut self, adjust: int) { + if adjust < 0 { + if self.multiline { + let remaining = self.edit_point.index; + if adjust.abs() as uint > remaining { + self.edit_point.index = 0; + self.adjust_vertical(-1); + self.edit_point.index = self.current_line_length(); + self.adjust_horizontal(adjust + remaining as int); + } else { + self.edit_point.index = (self.edit_point.index as int + adjust) as uint; + } + } else { + self.edit_point.index = max(0, self.edit_point.index as int + adjust) as uint; + } + } else { + if self.multiline { + let remaining = self.current_line_length() - self.edit_point.index; + if adjust as uint > remaining { + self.edit_point.index = 0; + self.adjust_vertical(1); + self.adjust_horizontal(adjust - remaining as int); + } else { + self.edit_point.index += adjust as uint; + } + } else { + self.edit_point.index = min(self.current_line_length(), + self.edit_point.index + adjust as uint); + } + } + } + + fn handle_return(&mut self) -> KeyReaction { + if !self.multiline { + return TriggerDefaultAction; + } + + //TODO: support replacing selection with newline + let prefix = self.get_current_line().as_slice().slice_chars(0, self.edit_point.index).to_string(); + let suffix = self.get_current_line().as_slice().slice_chars(self.edit_point.index, + self.current_line_length()).to_string(); + self.lines[self.edit_point.line] = prefix; + self.lines.insert(self.edit_point.line + 1, suffix); + return DispatchInput; + } + + pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction { + match event.Key().as_slice() { + c if c.len() == 1 => { + self.insert_char(c.char_at(0)); + return DispatchInput; + } + "Space" => { + self.insert_char(' '); + DispatchInput + } + "Delete" => { + self.delete_char(true); + DispatchInput + } + "Backspace" => { + self.delete_char(false); + DispatchInput + } + "ArrowLeft" => { + self.adjust_horizontal(-1); + Nothing + } + "ArrowRight" => { + self.adjust_horizontal(1); + Nothing + } + "ArrowUp" => { + self.adjust_vertical(-1); + Nothing + } + "ArrowDown" => { + self.adjust_vertical(1); + Nothing + } + "Enter" => self.handle_return(), + "Home" => { + self.edit_point.index = 0; + Nothing + } + "End" => { + self.edit_point.index = self.current_line_length(); + Nothing + } + "Tab" => TriggerDefaultAction, + _ => Nothing, + } + } + + pub fn get_content(&self) -> DOMString { + let mut content = "".to_string(); + for (i, line) in self.lines.iter().enumerate() { + content.push_str(line.as_slice()); + if i < self.lines.len() - 1 { + content.push('\n'); + } + } + content + } + + pub fn set_content(&mut self, content: DOMString) { + self.lines = if self.multiline { + content.as_slice().split('\n').map(|s| s.to_string()).collect() + } else { + vec!(content) + }; + self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1); + self.edit_point.index = min(self.edit_point.index, self.current_line_length() - 1); + } +} |