diff options
Diffstat (limited to 'components/script/dom/eventtarget.rs')
-rw-r--r-- | components/script/dom/eventtarget.rs | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs new file mode 100644 index 00000000000..4f2cba18def --- /dev/null +++ b/components/script/dom/eventtarget.rs @@ -0,0 +1,287 @@ +/* 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::callback::CallbackContainer; +use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; +use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; +use dom::bindings::error::{Fallible, InvalidState, report_pending_exception}; +use dom::bindings::js::JSRef; +use dom::bindings::trace::Traceable; +use dom::bindings::utils::{Reflectable, Reflector}; +use dom::event::Event; +use dom::eventdispatcher::dispatch_event; +use dom::node::NodeTypeId; +use dom::workerglobalscope::WorkerGlobalScopeId; +use dom::xmlhttprequest::XMLHttpRequestId; +use dom::virtualmethods::VirtualMethods; +use js::jsapi::{JS_CompileUCFunction, JS_GetFunctionObject, JS_CloneFunctionObject}; +use js::jsapi::{JSContext, JSObject}; +use servo_util::str::DOMString; +use libc::{c_char, size_t}; +use std::cell::RefCell; +use std::ptr; +use url::Url; + +use std::collections::hashmap::HashMap; + +#[deriving(PartialEq,Encodable)] +pub enum ListenerPhase { + Capturing, + Bubbling, +} + +#[deriving(PartialEq,Encodable)] +pub enum EventTargetTypeId { + NodeTargetTypeId(NodeTypeId), + WindowTypeId, + WorkerTypeId, + WorkerGlobalScopeTypeId(WorkerGlobalScopeId), + XMLHttpRequestTargetTypeId(XMLHttpRequestId) +} + +#[deriving(PartialEq, Encodable)] +pub enum EventListenerType { + Additive(EventListener), + Inline(EventListener), +} + +impl EventListenerType { + fn get_listener(&self) -> EventListener { + match *self { + Additive(listener) | Inline(listener) => listener + } + } +} + +#[deriving(PartialEq,Encodable)] +pub struct EventListenerEntry { + pub phase: ListenerPhase, + pub listener: EventListenerType +} + +#[deriving(Encodable)] +pub struct EventTarget { + pub type_id: EventTargetTypeId, + reflector_: Reflector, + handlers: Traceable<RefCell<HashMap<DOMString, Vec<EventListenerEntry>>>>, +} + +impl EventTarget { + pub fn new_inherited(type_id: EventTargetTypeId) -> EventTarget { + EventTarget { + type_id: type_id, + reflector_: Reflector::new(), + handlers: Traceable::new(RefCell::new(HashMap::new())), + } + } + + pub fn get_listeners(&self, type_: &str) -> Option<Vec<EventListener>> { + self.handlers.deref().borrow().find_equiv(&type_).map(|listeners| { + listeners.iter().map(|entry| entry.listener.get_listener()).collect() + }) + } + + pub fn get_listeners_for(&self, type_: &str, desired_phase: ListenerPhase) + -> Option<Vec<EventListener>> { + self.handlers.deref().borrow().find_equiv(&type_).map(|listeners| { + let filtered = listeners.iter().filter(|entry| entry.phase == desired_phase); + filtered.map(|entry| entry.listener.get_listener()).collect() + }) + } +} + +pub trait EventTargetHelpers { + fn dispatch_event_with_target<'a>(&self, + target: Option<JSRef<'a, EventTarget>>, + event: &JSRef<Event>) -> Fallible<bool>; + fn set_inline_event_listener(&self, + ty: DOMString, + listener: Option<EventListener>); + fn get_inline_event_listener(&self, ty: DOMString) -> Option<EventListener>; + fn set_event_handler_uncompiled(&self, + cx: *mut JSContext, + url: Url, + scope: *mut JSObject, + ty: &str, + source: DOMString); + fn set_event_handler_common<T: CallbackContainer>(&self, ty: &str, + listener: Option<T>); + fn get_event_handler_common<T: CallbackContainer>(&self, ty: &str) -> Option<T>; + + fn has_handlers(&self) -> bool; +} + +impl<'a> EventTargetHelpers for JSRef<'a, EventTarget> { + fn dispatch_event_with_target<'b>(&self, + target: Option<JSRef<'b, EventTarget>>, + event: &JSRef<Event>) -> Fallible<bool> { + if event.deref().dispatching.deref().get() || !event.deref().initialized.deref().get() { + return Err(InvalidState); + } + Ok(dispatch_event(self, target, event)) + } + + fn set_inline_event_listener(&self, + ty: DOMString, + listener: Option<EventListener>) { + let mut handlers = self.handlers.deref().borrow_mut(); + let entries = handlers.find_or_insert_with(ty, |_| vec!()); + let idx = entries.iter().position(|&entry| { + match entry.listener { + Inline(_) => true, + _ => false, + } + }); + + match idx { + Some(idx) => { + match listener { + Some(listener) => entries.get_mut(idx).listener = Inline(listener), + None => { + entries.remove(idx); + } + } + } + None => { + if listener.is_some() { + entries.push(EventListenerEntry { + phase: Bubbling, + listener: Inline(listener.unwrap()), + }); + } + } + } + } + + fn get_inline_event_listener(&self, ty: DOMString) -> Option<EventListener> { + let handlers = self.handlers.deref().borrow(); + let entries = handlers.find(&ty); + entries.and_then(|entries| entries.iter().find(|entry| { + match entry.listener { + Inline(_) => true, + _ => false, + } + }).map(|entry| entry.listener.get_listener())) + } + + fn set_event_handler_uncompiled(&self, + cx: *mut JSContext, + url: Url, + scope: *mut JSObject, + ty: &str, + source: DOMString) { + let url = url.serialize().to_c_str(); + let name = ty.to_c_str(); + let lineno = 0; //XXXjdm need to get a real number here + + let nargs = 1; //XXXjdm not true for onerror + static arg_name: [c_char, ..6] = + ['e' as c_char, 'v' as c_char, 'e' as c_char, 'n' as c_char, 't' as c_char, 0]; + static arg_names: [*const c_char, ..1] = [&arg_name as *const c_char]; + + let source: Vec<u16> = source.as_slice().utf16_units().collect(); + let handler = unsafe { + JS_CompileUCFunction(cx, + ptr::mut_null(), + name.as_ptr(), + nargs, + &arg_names as *const *const i8 as *mut *const i8, + source.as_ptr(), + source.len() as size_t, + url.as_ptr(), + lineno) + }; + if handler.is_null() { + report_pending_exception(cx, self.reflector().get_jsobject()); + return; + } + + let funobj = unsafe { + JS_CloneFunctionObject(cx, JS_GetFunctionObject(handler), scope) + }; + assert!(funobj.is_not_null()); + self.set_event_handler_common(ty, Some(EventHandlerNonNull::new(funobj))); + } + + fn set_event_handler_common<T: CallbackContainer>( + &self, ty: &str, listener: Option<T>) + { + let event_listener = listener.map(|listener| + EventListener::new(listener.callback())); + self.set_inline_event_listener(ty.to_string(), event_listener); + } + + fn get_event_handler_common<T: CallbackContainer>(&self, ty: &str) -> Option<T> { + let listener = self.get_inline_event_listener(ty.to_string()); + listener.map(|listener| CallbackContainer::new(listener.parent.callback())) + } + + fn has_handlers(&self) -> bool { + !self.handlers.deref().borrow().is_empty() + } +} + +impl<'a> EventTargetMethods for JSRef<'a, EventTarget> { + fn AddEventListener(&self, + ty: DOMString, + listener: Option<EventListener>, + capture: bool) { + match listener { + Some(listener) => { + let mut handlers = self.handlers.deref().borrow_mut(); + let entry = handlers.find_or_insert_with(ty, |_| vec!()); + let phase = if capture { Capturing } else { Bubbling }; + let new_entry = EventListenerEntry { + phase: phase, + listener: Additive(listener) + }; + if entry.as_slice().position_elem(&new_entry).is_none() { + entry.push(new_entry); + } + }, + _ => (), + } + } + + fn RemoveEventListener(&self, + ty: DOMString, + listener: Option<EventListener>, + capture: bool) { + match listener { + Some(listener) => { + let mut handlers = self.handlers.deref().borrow_mut(); + let mut entry = handlers.find_mut(&ty); + for entry in entry.mut_iter() { + let phase = if capture { Capturing } else { Bubbling }; + let old_entry = EventListenerEntry { + phase: phase, + listener: Additive(listener) + }; + let position = entry.as_slice().position_elem(&old_entry); + for &position in position.iter() { + entry.remove(position); + } + } + }, + _ => (), + } + } + + fn DispatchEvent(&self, event: &JSRef<Event>) -> Fallible<bool> { + self.dispatch_event_with_target(None, event) + } +} + +impl Reflectable for EventTarget { + fn reflector<'a>(&'a self) -> &'a Reflector { + &self.reflector_ + } +} + +impl<'a> VirtualMethods for JSRef<'a, EventTarget> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + None + } +} |