aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <metajack+bors@gmail.com>2014-11-13 10:57:33 -0700
committerbors-servo <metajack+bors@gmail.com>2014-11-13 10:57:33 -0700
commit2ffa845cf463b14b19322d477a77ffd20efa89a9 (patch)
tree64428ac6615df95fabb298f0aeb274fc10f947c7
parentc5e1b0d32e17fad29799023c85e2e73ac89c3af7 (diff)
parentc23edf6f5a020f24008c84957c1a290241632c6d (diff)
downloadservo-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.
-rw-r--r--components/compositing/compositor.rs13
-rw-r--r--components/compositing/constellation.rs15
-rw-r--r--components/compositing/windowing.rs4
-rw-r--r--components/msg/constellation_msg.rs143
-rw-r--r--components/script/dom/bindings/codegen/CodegenRust.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/WebIDL.py3
-rw-r--r--components/script/dom/bindings/codegen/parser/module.patch9
-rw-r--r--components/script/dom/customevent.rs6
-rw-r--r--components/script/dom/document.rs34
-rw-r--r--components/script/dom/element.rs26
-rw-r--r--components/script/dom/event.rs5
-rw-r--r--components/script/dom/htmlinputelement.rs82
-rw-r--r--components/script/dom/keyboardevent.rs639
-rw-r--r--components/script/dom/mouseevent.rs26
-rw-r--r--components/script/dom/uievent.rs4
-rw-r--r--components/script/dom/webidls/KeyboardEvent.webidl58
-rw-r--r--components/script/dom/webidls/MouseEvent.webidl6
-rw-r--r--components/script/dom/webidls/SharedMouseAndKeyboardEventInit.webidl23
-rw-r--r--components/script/dom/window.rs1
-rw-r--r--components/script/lib.rs2
-rw-r--r--components/script/script_task.rs75
-rw-r--r--components/script/textinput.rs299
-rw-r--r--components/script_traits/lib.rs5
-rw-r--r--ports/glfw/window.rs162
-rw-r--r--tests/content/test_interfaces.html1
-rw-r--r--tests/html/test_focus.html7
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>