diff options
author | bors-servo <metajack+bors@gmail.com> | 2014-11-13 10:57:33 -0700 |
---|---|---|
committer | bors-servo <metajack+bors@gmail.com> | 2014-11-13 10:57:33 -0700 |
commit | 2ffa845cf463b14b19322d477a77ffd20efa89a9 (patch) | |
tree | 64428ac6615df95fabb298f0aeb274fc10f947c7 | |
parent | c5e1b0d32e17fad29799023c85e2e73ac89c3af7 (diff) | |
parent | c23edf6f5a020f24008c84957c1a290241632c6d (diff) | |
download | servo-2ffa845cf463b14b19322d477a77ffd20efa89a9.tar.gz servo-2ffa845cf463b14b19322d477a77ffd20efa89a9.zip |
auto merge of #3585 : jdm/servo/input, r=gw
This attempts to implement a bunch of the DOM Level 3 Events spec by implementing the KeyboardEvent interface, the document focus context, and dispatching keyup/keydown/keypress events appropriately. There's also some support for multiline text input that's untested.
26 files changed, 1593 insertions, 69 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 95714929ad2..e20ea06f0e8 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -19,7 +19,7 @@ use windowing::{MouseWindowEvent, MouseWindowEventClass, MouseWindowMouseDownEve use windowing::{MouseWindowMouseUpEvent, MouseWindowMoveEventClass, NavigationWindowEvent}; use windowing::{QuitWindowEvent, RefreshWindowEvent, ResizeWindowEvent, ScrollWindowEvent}; use windowing::{WindowEvent, WindowMethods, WindowNavigateMsg, ZoomWindowEvent}; -use windowing::{PinchZoomWindowEvent}; +use windowing::{PinchZoomWindowEvent, KeyEvent}; use azure::azure_hl; use std::cmp; @@ -43,7 +43,7 @@ use servo_msg::compositor_msg::{Blank, Epoch, FinishedLoading, IdleRenderState, use servo_msg::compositor_msg::{ReadyState, RenderingRenderState, RenderState, Scrollable}; use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg}; use servo_msg::constellation_msg::{NavigateMsg, LoadData, PipelineId, ResizedWindowMsg}; -use servo_msg::constellation_msg::{WindowSizeData}; +use servo_msg::constellation_msg::{WindowSizeData, KeyState, Key, KeyModifiers}; use servo_msg::constellation_msg; use servo_util::geometry::{PagePx, ScreenPx, ViewportPx}; use servo_util::memory::MemoryProfilerChan; @@ -707,6 +707,10 @@ impl<Window: WindowMethods> IOCompositor<Window> { self.on_navigation_window_event(direction); } + KeyEvent(key, state, modifiers) => { + self.on_key_event(key, state, modifiers); + } + FinishedWindowEvent => { let exit = opts::get().exit_after_load; if exit { @@ -878,6 +882,11 @@ impl<Window: WindowMethods> IOCompositor<Window> { chan.send(NavigateMsg(direction)) } + fn on_key_event(&self, key: Key, state: KeyState, modifiers: KeyModifiers) { + let ConstellationChan(ref chan) = self.constellation_chan; + chan.send(constellation_msg::KeyEvent(key, state, modifiers)) + } + fn convert_buffer_requests_to_pipeline_requests_map(&self, requests: Vec<(Rc<Layer<CompositorData>>, Vec<BufferRequest>)>) -> diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index 7506d64b870..d8831c02c90 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -13,7 +13,8 @@ use gfx::render_task; use layers::geometry::DevicePixel; use layout_traits::{LayoutControlChan, LayoutTaskFactory, ExitNowMsg}; use libc; -use script_traits::{ResizeMsg, ResizeInactiveMsg, ExitPipelineMsg}; +use script_traits; +use script_traits::{ResizeMsg, ResizeInactiveMsg, ExitPipelineMsg, SendEventMsg}; use script_traits::{ScriptControlChan, ScriptTaskFactory}; use servo_msg::compositor_msg::LayerId; use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, FailureMsg, Failure, FrameRectMsg}; @@ -21,6 +22,7 @@ use servo_msg::constellation_msg::{IFrameSandboxState, IFrameUnsandboxed, InitLo use servo_msg::constellation_msg::{LoadCompleteMsg, LoadUrlMsg, LoadData, Msg, NavigateMsg}; use servo_msg::constellation_msg::{NavigationType, PipelineId, RendererReadyMsg, ResizedWindowMsg}; use servo_msg::constellation_msg::{ScriptLoadedURLInIFrameMsg, SubpageId, WindowSizeData}; +use servo_msg::constellation_msg::{KeyEvent, Key, KeyState, KeyModifiers}; use servo_msg::constellation_msg; use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient}; use servo_net::resource_task::ResourceTask; @@ -450,6 +452,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { debug!("constellation got window resize message"); self.handle_resized_window_msg(new_size); } + KeyEvent(key, state, modifiers) => { + debug!("constellation got key event message"); + self.handle_key_msg(key, state, modifiers); + } } true } @@ -761,6 +767,13 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { .any(|current_frame| current_frame.contains(pipeline_id)) } + fn handle_key_msg(&self, key: Key, state: KeyState, mods: KeyModifiers) { + self.current_frame().as_ref().map(|frame| { + let ScriptControlChan(ref chan) = frame.pipeline.script_chan; + chan.send(SendEventMsg(frame.pipeline.id, script_traits::KeyEvent(key, state, mods))); + }); + } + fn handle_renderer_ready_msg(&mut self, pipeline_id: PipelineId) { debug!("Renderer {} ready to send paint msg", pipeline_id); // This message could originate from a pipeline in the navigation context or diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 5815de6e2b5..e9b95f551b7 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -11,6 +11,7 @@ use geom::scale_factor::ScaleFactor; use geom::size::TypedSize2D; use layers::geometry::DevicePixel; use layers::platform::surface::NativeGraphicsMetadata; +use servo_msg::constellation_msg::{Key, KeyState, KeyModifiers}; use servo_msg::compositor_msg::{ReadyState, RenderState}; use servo_util::geometry::ScreenPx; use std::fmt::{FormatError, Formatter, Show}; @@ -58,6 +59,8 @@ pub enum WindowEvent { FinishedWindowEvent, /// Sent when the user quits the application QuitWindowEvent, + /// Sent when a key input state changes + KeyEvent(Key, KeyState, KeyModifiers), } impl Show for WindowEvent { @@ -66,6 +69,7 @@ impl Show for WindowEvent { IdleWindowEvent => write!(f, "Idle"), RefreshWindowEvent => write!(f, "Refresh"), ResizeWindowEvent(..) => write!(f, "Resize"), + KeyEvent(..) => write!(f, "Key"), LoadUrlWindowEvent(..) => write!(f, "LoadUrl"), MouseWindowEventClass(..) => write!(f, "Mouse"), MouseWindowMoveEventClass(..) => write!(f, "MouseMove"), diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index e043a04d3fd..f05c16dfb42 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -50,6 +50,148 @@ pub struct WindowSizeData { pub device_pixel_ratio: ScaleFactor<ViewportPx, DevicePixel, f32>, } +#[deriving(PartialEq)] +pub enum KeyState { + Pressed, + Released, + Repeated, +} + +//N.B. Straight up copied from glfw-rs +#[deriving(Show)] +pub enum Key { + KeySpace, + KeyApostrophe, + KeyComma, + KeyMinus, + KeyPeriod, + KeySlash, + Key0, + Key1, + Key2, + Key3, + Key4, + Key5, + Key6, + Key7, + Key8, + Key9, + KeySemicolon, + KeyEqual, + KeyA, + KeyB, + KeyC, + KeyD, + KeyE, + KeyF, + KeyG, + KeyH, + KeyI, + KeyJ, + KeyK, + KeyL, + KeyM, + KeyN, + KeyO, + KeyP, + KeyQ, + KeyR, + KeyS, + KeyT, + KeyU, + KeyV, + KeyW, + KeyX, + KeyY, + KeyZ, + KeyLeftBracket, + KeyBackslash, + KeyRightBracket, + KeyGraveAccent, + KeyWorld1, + KeyWorld2, + + KeyEscape, + KeyEnter, + KeyTab, + KeyBackspace, + KeyInsert, + KeyDelete, + KeyRight, + KeyLeft, + KeyDown, + KeyUp, + KeyPageUp, + KeyPageDown, + KeyHome, + KeyEnd, + KeyCapsLock, + KeyScrollLock, + KeyNumLock, + KeyPrintScreen, + KeyPause, + KeyF1, + KeyF2, + KeyF3, + KeyF4, + KeyF5, + KeyF6, + KeyF7, + KeyF8, + KeyF9, + KeyF10, + KeyF11, + KeyF12, + KeyF13, + KeyF14, + KeyF15, + KeyF16, + KeyF17, + KeyF18, + KeyF19, + KeyF20, + KeyF21, + KeyF22, + KeyF23, + KeyF24, + KeyF25, + KeyKp0, + KeyKp1, + KeyKp2, + KeyKp3, + KeyKp4, + KeyKp5, + KeyKp6, + KeyKp7, + KeyKp8, + KeyKp9, + KeyKpDecimal, + KeyKpDivide, + KeyKpMultiply, + KeyKpSubtract, + KeyKpAdd, + KeyKpEnter, + KeyKpEqual, + KeyLeftShift, + KeyLeftControl, + KeyLeftAlt, + KeyLeftSuper, + KeyRightShift, + KeyRightControl, + KeyRightAlt, + KeyRightSuper, + KeyMenu, +} + +bitflags! { + flags KeyModifiers: u8 { + const SHIFT = 0x01, + const CONTROL = 0x02, + const ALT = 0x04, + const SUPER = 0x08, + } +} + /// Messages from the compositor and script to the constellation. pub enum Msg { ExitMsg, @@ -62,6 +204,7 @@ pub enum Msg { NavigateMsg(NavigationDirection), RendererReadyMsg(PipelineId), ResizedWindowMsg(WindowSizeData), + KeyEvent(Key, KeyState, KeyModifiers), } /// Similar to net::resource_task::LoadData diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 9ca15459ba1..aaac17a9002 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -4280,7 +4280,7 @@ class CGDictionary(CGThing): d = self.dictionary if d.parent: inheritance = " pub parent: %s::%s<'a, 'b>,\n" % (self.makeModuleName(d.parent), - self.makeClassName(d.parent)) + self.makeClassName(d.parent)) else: inheritance = "" memberDecls = [" pub %s: %s," % @@ -4347,12 +4347,7 @@ class CGDictionary(CGThing): @staticmethod def makeModuleName(dictionary): - name = dictionary.identifier.name - if name.endswith('Init'): - return toBindingNamespace(name.replace('Init', '')) - #XXXjdm This breaks on the test webidl files, sigh. - #raise TypeError("No idea how to find this dictionary's definition: " + name) - return "/* uh oh */ %s" % name + return dictionary.module() def getMemberType(self, memberInfo): member, (_, _, declType, _) = memberInfo @@ -4535,7 +4530,7 @@ class CGBindingRoot(CGThing): 'dom::bindings::utils::{DOMJSClass, JSCLASS_DOM_GLOBAL}', 'dom::bindings::utils::{FindEnumStringIndex, GetArrayIndexFromId}', 'dom::bindings::utils::{GetPropertyOnPrototype, GetProtoOrIfaceArray}', - 'dom::bindings::utils::{HasPropertyOnPrototype, IntVal}', + 'dom::bindings::utils::{HasPropertyOnPrototype, IntVal, UintVal}', 'dom::bindings::utils::{Reflectable}', 'dom::bindings::utils::{squirrel_away_unique}', 'dom::bindings::utils::{ThrowingConstructor, unwrap, unwrap_jsmanaged}', @@ -5430,7 +5425,8 @@ class GlobalGenRoots(): def Bindings(config): descriptors = (set(d.name + "Binding" for d in config.getDescriptors(register=True)) | - set(d.unroll().module() for d in config.callbacks)) + set(d.unroll().module() for d in config.callbacks) | + set(d.module() for d in config.getDictionaries())) curr = CGList([CGGeneric("pub mod %s;\n" % name) for name in sorted(descriptors)]) curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) return curr diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py index 32f80e82c56..370ac7df7c0 100644 --- a/components/script/dom/bindings/codegen/parser/WebIDL.py +++ b/components/script/dom/bindings/codegen/parser/WebIDL.py @@ -1422,6 +1422,9 @@ class IDLDictionary(IDLObjectWithScope): self.identifier.name, [member.location] + locations) + def module(self): + return self.location.filename().split('/')[-1].split('.webidl')[0] + 'Binding' + def addExtendedAttributes(self, attrs): assert len(attrs) == 0 diff --git a/components/script/dom/bindings/codegen/parser/module.patch b/components/script/dom/bindings/codegen/parser/module.patch index 977947b4c63..f2ed1aff944 100644 --- a/components/script/dom/bindings/codegen/parser/module.patch +++ b/components/script/dom/bindings/codegen/parser/module.patch @@ -1,5 +1,14 @@ --- WebIDL.py +++ WebIDL.py +@@ -1422,6 +1422,9 @@ class IDLDictionary(IDLObjectWithScope): + self.identifier.name, + [member.location] + locations) + ++ def module(self): ++ return self.location.filename().split('/')[-1].split('.webidl')[0] + 'Binding' ++ + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 @@ -3398,6 +3398,9 @@ class IDLCallbackType(IDLType, IDLObjectWithScope): self._treatNonCallableAsNull = False self._treatNonObjectAsNull = False diff --git a/components/script/dom/customevent.rs b/components/script/dom/customevent.rs index 3cad0e2ddba..e1ac0125bb8 100644 --- a/components/script/dom/customevent.rs +++ b/components/script/dom/customevent.rs @@ -65,8 +65,12 @@ impl<'a> CustomEventMethods for JSRef<'a, CustomEvent> { can_bubble: bool, cancelable: bool, detail: JSVal) { - self.detail.set(detail); let event: JSRef<Event> = EventCast::from_ref(self); + if event.dispatching() { + return; + } + + self.detail.set(detail); event.InitEvent(type_, can_bubble, cancelable); } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 793bfad38b2..b5a39073dad 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -96,6 +96,10 @@ pub struct Document { anchors: MutNullableJS<HTMLCollection>, applets: MutNullableJS<HTMLCollection>, ready_state: Cell<DocumentReadyState>, + /// The element that has most recently requested focus for itself. + possibly_focused: MutNullableJS<Element>, + /// The element that currently has the document focus context. + focused: MutNullableJS<Element>, } impl DocumentDerived for EventTarget { @@ -178,6 +182,10 @@ pub trait DocumentHelpers<'a> { fn load_anchor_href(self, href: DOMString); fn find_fragment_node(self, fragid: DOMString) -> Option<Temporary<Element>>; fn set_ready_state(self, state: DocumentReadyState); + fn get_focused_element(self) -> Option<Temporary<Element>>; + fn begin_focus_transaction(self); + fn request_focus(self, elem: JSRef<Element>); + fn commit_focus_transaction(self); } impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { @@ -327,6 +335,30 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { let target: JSRef<EventTarget> = EventTargetCast::from_ref(self); let _ = target.DispatchEvent(*event); } + + /// Return the element that currently has focus. + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#events-focusevent-doc-focus + fn get_focused_element(self) -> Option<Temporary<Element>> { + self.focused.get() + } + + /// Initiate a new round of checking for elements requesting focus. The last element to call + /// `request_focus` before `commit_focus_transaction` is called will receive focus. + fn begin_focus_transaction(self) { + self.possibly_focused.clear(); + } + + /// Request that the given element receive focus once the current transaction is complete. + fn request_focus(self, elem: JSRef<Element>) { + self.possibly_focused.assign(Some(elem)) + } + + /// Reassign the focus context to the element that last requested focus during this + /// transaction, or none if no elements requested it. + fn commit_focus_transaction(self) { + //TODO: dispatch blur, focus, focusout, and focusin events + self.focused.assign(self.possibly_focused.get()); + } } #[deriving(PartialEq)] @@ -390,6 +422,8 @@ impl Document { anchors: Default::default(), applets: Default::default(), ready_state: Cell::new(ready_state), + possibly_focused: Default::default(), + focused: Default::default(), } } 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/event.rs b/components/script/dom/event.rs index d6dc2cfe00f..f04b6d0a055 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -29,7 +29,7 @@ pub enum EventPhase { pub enum EventTypeId { CustomEventTypeId, HTMLEventTypeId, - KeyEventTypeId, + KeyboardEventTypeId, MessageEventTypeId, MouseEventTypeId, ProgressEventTypeId, @@ -219,10 +219,11 @@ impl<'a> EventMethods for JSRef<'a, Event> { type_: DOMString, bubbles: bool, cancelable: bool) { - self.initialized.set(true); if self.dispatching.get() { return; } + + self.initialized.set(true); self.stop_propagation.set(false); self.stop_immediate.set(false); self.canceled.set(false); diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index f12e75db38d..9c7050ec8a1 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::{Single, 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(Single, "".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") => { @@ -415,6 +432,23 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { } _ => {} } + + //TODO: set the editing position for text inputs + + 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 new file mode 100644 index 00000000000..b48a3295299 --- /dev/null +++ b/components/script/dom/keyboardevent.rs @@ -0,0 +1,639 @@ +/* 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/. */ + +use dom::bindings::codegen::Bindings::KeyboardEventBinding; +use dom::bindings::codegen::Bindings::KeyboardEventBinding::{KeyboardEventMethods, KeyboardEventConstants}; +use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; +use dom::bindings::codegen::InheritTypes::{EventCast, UIEventCast, KeyboardEventDerived}; +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::global; +use dom::bindings::js::{JSRef, Temporary, RootedReference}; +use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; +use dom::event::{Event, KeyboardEventTypeId}; +use dom::uievent::UIEvent; +use dom::window::Window; +use servo_msg::constellation_msg; +use servo_util::str::DOMString; +use std::cell::{RefCell, Cell}; + +#[jstraceable] +#[must_root] +pub struct KeyboardEvent { + uievent: UIEvent, + key: RefCell<DOMString>, + code: RefCell<DOMString>, + location: Cell<u32>, + ctrl: Cell<bool>, + alt: Cell<bool>, + shift: Cell<bool>, + meta: Cell<bool>, + repeat: Cell<bool>, + is_composing: Cell<bool>, + char_code: Cell<Option<u32>>, + key_code: Cell<u32>, +} + +impl KeyboardEventDerived for Event { + fn is_keyboardevent(&self) -> bool { + *self.type_id() == KeyboardEventTypeId + } +} + +impl KeyboardEvent { + fn new_inherited() -> KeyboardEvent { + KeyboardEvent { + uievent: UIEvent::new_inherited(KeyboardEventTypeId), + key: RefCell::new("".to_string()), + code: RefCell::new("".to_string()), + location: Cell::new(0), + ctrl: Cell::new(false), + alt: Cell::new(false), + shift: Cell::new(false), + meta: Cell::new(false), + repeat: Cell::new(false), + is_composing: Cell::new(false), + char_code: Cell::new(None), + key_code: Cell::new(0), + } + } + + fn new_uninitialized(window: JSRef<Window>) -> Temporary<KeyboardEvent> { + reflect_dom_object(box KeyboardEvent::new_inherited(), + &global::Window(window), + KeyboardEventBinding::Wrap) + } + + pub fn new(window: JSRef<Window>, + type_: DOMString, + canBubble: bool, + cancelable: bool, + view: Option<JSRef<Window>>, + _detail: i32, + key: DOMString, + code: DOMString, + location: u32, + repeat: bool, + isComposing: bool, + ctrlKey: bool, + altKey: bool, + shiftKey: bool, + metaKey: bool, + char_code: Option<u32>, + key_code: u32) -> Temporary<KeyboardEvent> { + let ev = KeyboardEvent::new_uninitialized(window).root(); + ev.deref().InitKeyboardEvent(type_, canBubble, cancelable, view, key, location, + "".to_string(), repeat, "".to_string()); + *ev.code.borrow_mut() = code; + ev.ctrl.set(ctrlKey); + ev.alt.set(altKey); + ev.shift.set(shiftKey); + ev.meta.set(metaKey); + ev.char_code.set(char_code); + ev.key_code.set(key_code); + ev.is_composing.set(isComposing); + Temporary::from_rooted(*ev) + } + + pub fn Constructor(global: &GlobalRef, + type_: DOMString, + init: &KeyboardEventBinding::KeyboardEventInit) -> Fallible<Temporary<KeyboardEvent>> { + let event = KeyboardEvent::new(global.as_window(), type_, + init.parent.parent.parent.bubbles, + init.parent.parent.parent.cancelable, + init.parent.parent.view.root_ref(), + init.parent.parent.detail, + init.key.clone(), init.code.clone(), init.location, + init.repeat, init.isComposing, init.parent.ctrlKey, + init.parent.altKey, init.parent.shiftKey, init.parent.metaKey, + None, 0); + Ok(event) + } + + 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_charcode(key, mods), + key_code: key_keycode(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_charcode(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_keycode(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: &'static str, + pub code: &'static str, + pub location: u32, + pub char_code: Option<u32>, + pub key_code: u32, +} + +impl KeyEventProperties { + pub fn is_printable(&self) -> bool { + self.char_code.is_some() + } +} + +impl<'a> KeyboardEventMethods for JSRef<'a, KeyboardEvent> { + fn InitKeyboardEvent(self, + typeArg: DOMString, + canBubbleArg: bool, + cancelableArg: bool, + viewArg: Option<JSRef<Window>>, + keyArg: DOMString, + locationArg: u32, + _modifiersListArg: DOMString, + repeat: bool, + _locale: DOMString) { + let event: JSRef<Event> = EventCast::from_ref(self); + if event.dispatching() { + return; + } + + let uievent: JSRef<UIEvent> = UIEventCast::from_ref(self); + uievent.InitUIEvent(typeArg, canBubbleArg, cancelableArg, viewArg, 0); + *self.key.borrow_mut() = keyArg; + self.location.set(locationArg); + self.repeat.set(repeat); + } + + fn Key(self) -> DOMString { + self.key.borrow().clone() + } + + fn Code(self) -> DOMString { + self.code.borrow().clone() + } + + fn Location(self) -> u32 { + self.location.get() + } + + fn CtrlKey(self) -> bool { + self.ctrl.get() + } + + fn ShiftKey(self) -> bool { + self.shift.get() + } + + fn AltKey(self) -> bool { + self.alt.get() + } + + fn MetaKey(self) -> bool { + self.meta.get() + } + + fn Repeat(self) -> bool { + self.repeat.get() + } + + fn IsComposing(self) -> bool { + self.is_composing.get() + } + + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#widl-KeyboardEvent-getModifierState + fn GetModifierState(self, keyArg: DOMString) -> bool { + match keyArg.as_slice() { + "Ctrl" => self.CtrlKey(), + "Alt" => self.AltKey(), + "Shift" => self.ShiftKey(), + "Meta" => self.MetaKey(), + "AltGraph" | "CapsLock" | "NumLock" | "ScrollLock" | "Accel" | + "Fn" | "FnLock" | "Hyper" | "OS" | "Symbol" | "SymbolLock" => false, //FIXME + _ => false, + } + } + + fn CharCode(self) -> u32 { + self.char_code.get().unwrap_or(0) + } + + fn KeyCode(self) -> u32 { + self.key_code.get() + } + + fn Which(self) -> u32 { + self.char_code.get().unwrap_or(self.KeyCode()) + } +} + +impl Reflectable for KeyboardEvent { + fn reflector<'a>(&'a self) -> &'a Reflector { + self.uievent.reflector() + } +} diff --git a/components/script/dom/mouseevent.rs b/components/script/dom/mouseevent.rs index a4d3a0a9e09..81d23bf8afb 100644 --- a/components/script/dom/mouseevent.rs +++ b/components/script/dom/mouseevent.rs @@ -5,7 +5,7 @@ use dom::bindings::codegen::Bindings::MouseEventBinding; use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; -use dom::bindings::codegen::InheritTypes::{UIEventCast, MouseEventDerived}; +use dom::bindings::codegen::InheritTypes::{EventCast, UIEventCast, MouseEventDerived}; use dom::bindings::error::Fallible; use dom::bindings::global::GlobalRef; use dom::bindings::global; @@ -21,7 +21,7 @@ use std::default::Default; #[dom_struct] pub struct MouseEvent { - mouseevent: UIEvent, + uievent: UIEvent, screen_x: Cell<i32>, screen_y: Cell<i32>, client_x: Cell<i32>, @@ -43,7 +43,7 @@ impl MouseEventDerived for Event { impl MouseEvent { fn new_inherited() -> MouseEvent { MouseEvent { - mouseevent: UIEvent::new_inherited(MouseEventTypeId), + uievent: UIEvent::new_inherited(MouseEventTypeId), screen_x: Cell::new(0), screen_y: Cell::new(0), client_x: Cell::new(0), @@ -91,13 +91,13 @@ impl MouseEvent { type_: DOMString, init: &MouseEventBinding::MouseEventInit) -> Fallible<Temporary<MouseEvent>> { let event = MouseEvent::new(global.as_window(), type_, - init.parent.parent.bubbles, - init.parent.parent.cancelable, - init.parent.view.root_ref(), - init.parent.detail, + init.parent.parent.parent.bubbles, + init.parent.parent.parent.cancelable, + init.parent.parent.view.root_ref(), + init.parent.parent.detail, init.screenX, init.screenY, - init.clientX, init.clientY, init.ctrlKey, - init.altKey, init.shiftKey, init.metaKey, + init.clientX, init.clientY, init.parent.ctrlKey, + init.parent.altKey, init.parent.shiftKey, init.parent.metaKey, init.button, init.relatedTarget.root_ref()); Ok(event) } @@ -160,6 +160,11 @@ impl<'a> MouseEventMethods for JSRef<'a, MouseEvent> { metaKeyArg: bool, buttonArg: i16, relatedTargetArg: Option<JSRef<EventTarget>>) { + let event: JSRef<Event> = EventCast::from_ref(self); + if event.dispatching() { + return; + } + let uievent: JSRef<UIEvent> = UIEventCast::from_ref(self); uievent.InitUIEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg); self.screen_x.set(screenXArg); @@ -175,9 +180,8 @@ impl<'a> MouseEventMethods for JSRef<'a, MouseEvent> { } } - impl Reflectable for MouseEvent { fn reflector<'a>(&'a self) -> &'a Reflector { - self.mouseevent.reflector() + self.uievent.reflector() } } diff --git a/components/script/dom/uievent.rs b/components/script/dom/uievent.rs index 6a55f6ccbf8..036de7bfb64 100644 --- a/components/script/dom/uievent.rs +++ b/components/script/dom/uievent.rs @@ -89,6 +89,10 @@ impl<'a> UIEventMethods for JSRef<'a, UIEvent> { view: Option<JSRef<Window>>, detail: i32) { let event: JSRef<Event> = EventCast::from_ref(self); + if event.dispatching() { + return; + } + event.InitEvent(type_, can_bubble, cancelable); self.view.assign(view); self.detail.set(detail); diff --git a/components/script/dom/webidls/KeyboardEvent.webidl b/components/script/dom/webidls/KeyboardEvent.webidl new file mode 100644 index 00000000000..d0a185a14de --- /dev/null +++ b/components/script/dom/webidls/KeyboardEvent.webidl @@ -0,0 +1,58 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * The origin of this IDL file is + * https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#interface-KeyboardEvent + * + */ + +[Constructor(DOMString typeArg, optional KeyboardEventInit keyboardEventInitDict)] +interface KeyboardEvent : UIEvent { + // KeyLocationCode + const unsigned long DOM_KEY_LOCATION_STANDARD = 0x00; + const unsigned long DOM_KEY_LOCATION_LEFT = 0x01; + const unsigned long DOM_KEY_LOCATION_RIGHT = 0x02; + const unsigned long DOM_KEY_LOCATION_NUMPAD = 0x03; + readonly attribute DOMString key; + readonly attribute DOMString code; + readonly attribute unsigned long location; + readonly attribute boolean ctrlKey; + readonly attribute boolean shiftKey; + readonly attribute boolean altKey; + readonly attribute boolean metaKey; + readonly attribute boolean repeat; + readonly attribute boolean isComposing; + boolean getModifierState (DOMString keyArg); +}; + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-interface-KeyboardEvent-initializers +partial interface KeyboardEvent { + // Originally introduced (and deprecated) in DOM Level 3 + void initKeyboardEvent (DOMString typeArg, boolean bubblesArg, boolean cancelableArg, Window? viewArg, DOMString keyArg, unsigned long locationArg, DOMString modifiersListArg, boolean repeat, DOMString locale); +}; + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#KeyboardEvent-supplemental-interface +partial interface KeyboardEvent { + // The following support legacy user agents + readonly attribute unsigned long charCode; + readonly attribute unsigned long keyCode; + readonly attribute unsigned long which; +}; + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#interface-KeyboardEvent +dictionary KeyboardEventInit : SharedKeyboardAndMouseEventInit { + DOMString key = ""; + DOMString code = ""; + unsigned long location = 0; + boolean repeat = false; + boolean isComposing = false; +}; + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#events-KeyboardEventInit-supplemental +/*partial dictionary KeyboardEventInit { + unsigned long charCode = 0; + unsigned long keyCode = 0; + unsigned long which = 0; +};*/ diff --git a/components/script/dom/webidls/MouseEvent.webidl b/components/script/dom/webidls/MouseEvent.webidl index cdef58228c1..a46a44938ca 100644 --- a/components/script/dom/webidls/MouseEvent.webidl +++ b/components/script/dom/webidls/MouseEvent.webidl @@ -22,15 +22,11 @@ interface MouseEvent : UIEvent { }; // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-MouseEventInit -dictionary MouseEventInit : UIEventInit { +dictionary MouseEventInit : SharedKeyboardAndMouseEventInit { long screenX = 0; long screenY = 0; long clientX = 0; long clientY = 0; - boolean ctrlKey = false; - boolean shiftKey = false; - boolean altKey = false; - boolean metaKey = false; short button = 0; //unsigned short buttons = 0; EventTarget? relatedTarget = null; diff --git a/components/script/dom/webidls/SharedMouseAndKeyboardEventInit.webidl b/components/script/dom/webidls/SharedMouseAndKeyboardEventInit.webidl new file mode 100644 index 00000000000..eb852604ed1 --- /dev/null +++ b/components/script/dom/webidls/SharedMouseAndKeyboardEventInit.webidl @@ -0,0 +1,23 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-SharedKeyboardAndMouseEventInit +dictionary SharedKeyboardAndMouseEventInit : UIEventInit { + boolean ctrlKey = false; + boolean shiftKey = false; + boolean altKey = false; + boolean metaKey = false; + boolean keyModifierStateAltGraph = false; + boolean keyModifierStateCapsLock = false; + boolean keyModifierStateFn = false; + boolean keyModifierStateFnLock = false; + boolean keyModifierStateHyper = false; + boolean keyModifierStateNumLock = false; + boolean keyModifierStateOS = false; + boolean keyModifierStateScrollLock = false; + boolean keyModifierStateSuper = false; + boolean keyModifierStateSymbol = false; + boolean keyModifierStateSymbolLock = false; +}; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index b4318b69fa3..a3664207b85 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -382,6 +382,7 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { fn handle_fire_timer(self, timer_id: TimerId, cx: *mut JSContext) { let this_value = self.reflector().get_jsobject(); self.timers.fire_timer(timer_id, this_value, cx); + self.flush_layout(); } } diff --git a/components/script/lib.rs b/components/script/lib.rs index 1d64397e20e..123417e9429 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -178,6 +178,7 @@ pub mod dom { pub mod htmlulistelement; pub mod htmlvideoelement; pub mod htmlunknownelement; + pub mod keyboardevent; pub mod location; pub mod messageevent; pub mod mouseevent; @@ -220,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 c6f4b636d50..087e3fdf043 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -9,7 +9,9 @@ use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyStateValues}; use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; +use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, EventCast, ElementCast}; use dom::bindings::conversions; use dom::bindings::conversions::{FromJSValConvertible, Empty}; @@ -23,6 +25,7 @@ use dom::element::{HTMLSelectElementTypeId, HTMLTextAreaElementTypeId, HTMLOptio use dom::event::{Event, Bubbles, DoesNotBubble, Cancelable, NotCancelable}; use dom::uievent::UIEvent; use dom::eventtarget::{EventTarget, EventTargetHelpers}; +use dom::keyboardevent::KeyboardEvent; use dom::node; use dom::node::{ElementNodeTypeId, Node, NodeHelpers}; use dom::window::{Window, WindowHelpers}; @@ -42,11 +45,13 @@ use script_traits::{CompositorEvent, ResizeEvent, ReflowEvent, ClickEvent, Mouse use script_traits::{MouseMoveEvent, MouseUpEvent, ConstellationControlMsg, ScriptTaskFactory}; use script_traits::{ResizeMsg, AttachLayoutMsg, LoadMsg, ViewportMsg, SendEventMsg}; use script_traits::{ResizeInactiveMsg, ExitPipelineMsg, NewLayoutInfo, OpaqueScriptLayoutChannel}; -use script_traits::{ScriptControlChan, ReflowCompleteMsg, UntrustedNodeAddress}; +use script_traits::{ScriptControlChan, ReflowCompleteMsg, UntrustedNodeAddress, KeyEvent}; 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}; +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::{Released}; use servo_msg::constellation_msg; use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::ResourceTask; @@ -907,7 +912,67 @@ impl ScriptTask { MouseMoveEvent(point) => { self.handle_mouse_move_event(pipeline_id, point); } + + KeyEvent(key, state, modifiers) => { + self.dispatch_key_event(key, state, modifiers, pipeline_id); + } + } + } + + /// The entry point for all key processing for web content + fn dispatch_key_event(&self, key: Key, + state: KeyState, + modifiers: KeyModifiers, + pipeline_id: PipelineId) { + let page = get_page(&*self.page.borrow(), pipeline_id); + let frame = page.frame(); + let window = frame.as_ref().unwrap().window.root(); + let doc = window.Document().root(); + let focused = doc.get_focused_element().root(); + let body = doc.GetBody().root(); + + let target: JSRef<EventTarget> = match (&focused, &body) { + (&Some(ref focused), _) => EventTargetCast::from_ref(**focused), + (&None, &Some(ref body)) => EventTargetCast::from_ref(**body), + (&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 is_composing = false; + let is_repeating = state == Repeated; + let ev_type = match state { + Pressed | Repeated => "keydown", + Released => "keyup", + }.to_string(); + + let props = KeyboardEvent::key_properties(key, modifiers); + + let keyevent = KeyboardEvent::new(*window, ev_type, true, true, Some(*window), 0, + props.key.to_string(), props.code.to_string(), + props.location, is_repeating, is_composing, + ctrl, alt, shift, meta, + None, props.key_code).root(); + let event = EventCast::from_ref(*keyevent); + let _ = target.DispatchEvent(event); + + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#keys-cancelable-keys + if state != Released && props.is_printable() && !event.DefaultPrevented() { + // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#keypress-event-order + let event = KeyboardEvent::new(*window, "keypress".to_string(), true, true, Some(*window), + 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)); + + // TODO: if keypress event is canceled, prevent firing input events } + + window.flush_layout(); } /// The entry point for content to notify that a new load has been requested @@ -1011,6 +1076,9 @@ impl ScriptTask { match *page.frame() { Some(ref frame) => { let window = frame.window.root(); + let doc = window.Document().root(); + doc.begin_focus_transaction(); + let event = Event::new(&global::Window(*window), "click".to_string(), @@ -1018,6 +1086,7 @@ impl ScriptTask { let eventtarget: JSRef<EventTarget> = EventTargetCast::from_ref(node); let _ = eventtarget.dispatch_event_with_target(None, *event); + doc.commit_focus_transaction(); window.flush_layout(); } None => {} @@ -1093,7 +1162,7 @@ impl ScriptTask { } None => {} - } + } } } diff --git a/components/script/textinput.rs b/components/script/textinput.rs new file mode 100644 index 00000000000..940b9919e76 --- /dev/null +++ b/components/script/textinput.rs @@ -0,0 +1,299 @@ +/* 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 { + /// 0-based line number + line: uint, + /// 0-based column number + index: uint, +} + +/// Encapsulated state for handling keyboard input in a single or multiline text input control. +#[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 a multiline input? + multiline: bool, +} + +/// Resulting action to be taken by the owner of a text input that is handling an event. +pub enum KeyReaction { + TriggerDefaultAction, + DispatchInput, + Nothing, +} + +impl Default for TextPoint { + fn default() -> TextPoint { + TextPoint { + line: 0, + index: 0, + } + } +} + +/// Control whether this control should allow multiple lines. +#[deriving(PartialEq)] +pub enum Lines { + Single, + Multiple, +} + +/// The direction in which to delete a character. +#[deriving(PartialEq)] +enum DeleteDir { + Forward, + Backward +} + +impl TextInput { + /// Instantiate a new text input control + pub fn new(lines: Lines, initial: DOMString) -> TextInput { + let mut i = TextInput { + lines: vec!(), + edit_point: Default::default(), + _selection: None, + multiline: lines == Multiple, + }; + i.set_content(initial); + i + } + + /// Return the current line under the editing point + fn get_current_line(&self) -> &DOMString { + &self.lines[self.edit_point.line] + } + + /// Insert a character at the current editing point + 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; + } + + /// Remove a character at the current editing point + fn delete_char(&mut self, dir: DeleteDir) { + let forward = dir == Forward; + + //TODO: handle deleting selection + let prefix_end = if forward { + self.edit_point.index + } else { + if self.multiline { + //TODO: handle backspacing from position 0 of current line + if self.edit_point.index == 0 { + return; + } + } 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 + if is_eol { + return; + } + } 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); + } + } + + /// Return the length of the current line under the editing point. + fn current_line_length(&self) -> uint { + self.lines[self.edit_point.line].len() + } + + /// Adjust the editing point position by a given of lines. The resulting column is + /// as close to the original column position as possible. + 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); + } + + /// Adjust the editing point position by a given number of columns. If the adjustment + /// requested is larger than is available in the current line, the editing point is + /// adjusted vertically and the process repeats with the remaining adjustment requested. + 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); + } + } + } + + /// Deal with a newline input. + 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; + } + + /// Process a given `KeyboardEvent` and return an action for the caller to execute. + pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction { + match event.Key().as_slice() { + // printable characters have single-character key values + c if c.len() == 1 => { + self.insert_char(c.char_at(0)); + return DispatchInput; + } + "Space" => { + self.insert_char(' '); + DispatchInput + } + "Delete" => { + self.delete_char(Forward); + DispatchInput + } + "Backspace" => { + self.delete_char(Backward); + 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, + } + } + + /// Get the current contents of the text input. Multiple lines are joined by \n. + 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 + } + + /// Set the current contents of the text input. If this is control supports multiple lines, + /// any \n encountered will be stripped and force a new logical line. + 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); + } +} diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index beedd0895d3..10bc1964e01 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -25,7 +25,7 @@ extern crate serialize; use devtools_traits::DevtoolsControlChan; use libc::c_void; use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, WindowSizeData}; -use servo_msg::constellation_msg::{LoadData, SubpageId}; +use servo_msg::constellation_msg::{LoadData, SubpageId, Key, KeyState, KeyModifiers}; use servo_msg::compositor_msg::ScriptListener; use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::ResourceTask; @@ -74,7 +74,8 @@ pub enum CompositorEvent { ClickEvent(uint, Point2D<f32>), MouseDownEvent(uint, Point2D<f32>), MouseUpEvent(uint, Point2D<f32>), - MouseMoveEvent(Point2D<f32>) + MouseMoveEvent(Point2D<f32>), + KeyEvent(Key, KeyState, KeyModifiers), } /// An opaque wrapper around script<->layout channels to avoid leaking message types into diff --git a/ports/glfw/window.rs b/ports/glfw/window.rs index ffc2a08497e..0fe1b30f4b2 100644 --- a/ports/glfw/window.rs +++ b/ports/glfw/window.rs @@ -10,7 +10,7 @@ use alert::{Alert, AlertMethods}; use compositing::compositor_task::{mod, CompositorProxy, CompositorReceiver}; use compositing::windowing::{Forward, Back}; use compositing::windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent}; -use compositing::windowing::{MouseWindowClickEvent, MouseWindowMouseDownEvent}; +use compositing::windowing::{KeyEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent}; use compositing::windowing::{MouseWindowEventClass, MouseWindowMoveEventClass}; use compositing::windowing::{MouseWindowMouseUpEvent, RefreshWindowEvent}; use compositing::windowing::{NavigationWindowEvent, ScrollWindowEvent, ZoomWindowEvent}; @@ -25,6 +25,7 @@ use layers::platform::surface::NativeGraphicsMetadata; use libc::c_int; use msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState}; use msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState}; +use msg::constellation_msg; use std::cell::{Cell, RefCell}; use std::comm::Receiver; use std::rc::Rc; @@ -207,8 +208,16 @@ impl Window { match event { glfw::KeyEvent(key, _, action, mods) => { if action == glfw::Press { - self.handle_key(key, mods) + self.handle_key(key, mods); } + let key = glfw_key_to_script_key(key); + let state = match action { + glfw::Press => constellation_msg::Pressed, + glfw::Release => constellation_msg::Released, + glfw::Repeat => constellation_msg::Repeated, + }; + let modifiers = glfw_mods_to_script_mods(mods); + self.event_queue.borrow_mut().push(KeyEvent(key, state, modifiers)); }, glfw::FramebufferSizeEvent(width, height) => { self.event_queue.borrow_mut().push( @@ -428,3 +437,152 @@ extern "C" fn on_framebuffer_size(_glfw_window: *mut glfw::ffi::GLFWwindow, } } +fn glfw_mods_to_script_mods(mods: glfw::Modifiers) -> constellation_msg::KeyModifiers { + let mut result = constellation_msg::KeyModifiers::from_bits(0).unwrap(); + if mods.contains(glfw::Shift) { + result.insert(constellation_msg::SHIFT); + } + if mods.contains(glfw::Alt) { + result.insert(constellation_msg::ALT); + } + if mods.contains(glfw::Control) { + result.insert(constellation_msg::CONTROL); + } + if mods.contains(glfw::Super) { + result.insert(constellation_msg::SUPER); + } + result +} + +macro_rules! glfw_keys_to_script_keys( + ($key:expr, $($name:ident),+) => ( + match $key { + $(glfw::$name => constellation_msg::$name,)+ + } + ); +) + +fn glfw_key_to_script_key(key: glfw::Key) -> constellation_msg::Key { + glfw_keys_to_script_keys!(key, + KeySpace, + KeyApostrophe, + KeyComma, + KeyMinus, + KeyPeriod, + KeySlash, + Key0, + Key1, + Key2, + Key3, + Key4, + Key5, + Key6, + Key7, + Key8, + Key9, + KeySemicolon, + KeyEqual, + KeyA, + KeyB, + KeyC, + KeyD, + KeyE, + KeyF, + KeyG, + KeyH, + KeyI, + KeyJ, + KeyK, + KeyL, + KeyM, + KeyN, + KeyO, + KeyP, + KeyQ, + KeyR, + KeyS, + KeyT, + KeyU, + KeyV, + KeyW, + KeyX, + KeyY, + KeyZ, + KeyLeftBracket, + KeyBackslash, + KeyRightBracket, + KeyGraveAccent, + KeyWorld1, + KeyWorld2, + + KeyEscape, + KeyEnter, + KeyTab, + KeyBackspace, + KeyInsert, + KeyDelete, + KeyRight, + KeyLeft, + KeyDown, + KeyUp, + KeyPageUp, + KeyPageDown, + KeyHome, + KeyEnd, + KeyCapsLock, + KeyScrollLock, + KeyNumLock, + KeyPrintScreen, + KeyPause, + KeyF1, + KeyF2, + KeyF3, + KeyF4, + KeyF5, + KeyF6, + KeyF7, + KeyF8, + KeyF9, + KeyF10, + KeyF11, + KeyF12, + KeyF13, + KeyF14, + KeyF15, + KeyF16, + KeyF17, + KeyF18, + KeyF19, + KeyF20, + KeyF21, + KeyF22, + KeyF23, + KeyF24, + KeyF25, + KeyKp0, + KeyKp1, + KeyKp2, + KeyKp3, + KeyKp4, + KeyKp5, + KeyKp6, + KeyKp7, + KeyKp8, + KeyKp9, + KeyKpDecimal, + KeyKpDivide, + KeyKpMultiply, + KeyKpSubtract, + KeyKpAdd, + KeyKpEnter, + KeyKpEqual, + KeyLeftShift, + KeyLeftControl, + KeyLeftAlt, + KeyLeftSuper, + KeyRightShift, + KeyRightControl, + KeyRightAlt, + KeyRightSuper, + KeyMenu) +} diff --git a/tests/content/test_interfaces.html b/tests/content/test_interfaces.html index 696d6dcc7da..1a1fa9f1cd0 100644 --- a/tests/content/test_interfaces.html +++ b/tests/content/test_interfaces.html @@ -142,6 +142,7 @@ var interfaceNamesInGlobalScope = [ "HTMLUListElement", "HTMLUnknownElement", "HTMLVideoElement", + "KeyboardEvent", "Location", "MessageEvent", "MouseEvent", diff --git a/tests/html/test_focus.html b/tests/html/test_focus.html new file mode 100644 index 00000000000..967030b2a78 --- /dev/null +++ b/tests/html/test_focus.html @@ -0,0 +1,7 @@ +<body> +<input id="focused"> +<script> + document.body.addEventListener('keydown', function() { alert("body"); }, false); + document.getElementById('focused').addEventListener('keydown', function() { alert("input"); }, false); +</script> +</body> |