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.
This commit is contained in:
bors-servo 2014-11-13 10:57:33 -07:00
commit 2ffa845cf4
26 changed files with 1593 additions and 69 deletions

View file

@ -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>)>) ->

View file

@ -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

View file

@ -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"),

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
}
}

View file

@ -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(),
}
}

View file

@ -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> {

View file

@ -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);

View file

@ -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 => (),
}
});
}
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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);

View file

@ -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;
};*/

View file

@ -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;

View file

@ -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;
};

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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,9 +912,69 @@ 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
/// for the given pipeline.
fn trigger_load(&self, pipeline_id: PipelineId, load_data: LoadData) {
@ -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 => {}
}
}
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -142,6 +142,7 @@ var interfaceNamesInGlobalScope = [
"HTMLUListElement",
"HTMLUnknownElement",
"HTMLVideoElement",
"KeyboardEvent",
"Location",
"MessageEvent",
"MouseEvent",

View file

@ -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>