/* 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 crate::dom::beforeunloadevent::BeforeUnloadEvent; use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling}; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventMethods; use crate::dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods; use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull; use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; use crate::dom::bindings::codegen::Bindings::EventTargetBinding::AddEventListenerOptions; use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventListenerOptions; use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; use crate::dom::bindings::codegen::Bindings::EventTargetBinding::Wrap; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::UnionTypes::AddEventListenerOptionsOrBoolean; use crate::dom::bindings::codegen::UnionTypes::EventListenerOptionsOrBoolean; use crate::dom::bindings::codegen::UnionTypes::EventOrString; use crate::dom::bindings::error::{report_pending_exception, Error, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::element::Element; use crate::dom::errorevent::ErrorEvent; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::globalscope::GlobalScope; use crate::dom::node::document_from_node; use crate::dom::virtualmethods::VirtualMethods; use crate::dom::window::Window; use dom_struct::dom_struct; use fnv::FnvHasher; use js::jsapi::{JSAutoCompartment, JSFunction, JS_GetFunctionObject}; use js::rust::wrappers::CompileFunction; use js::rust::{AutoObjectVectorWrapper, CompileOptionsWrapper}; use libc::{c_char, size_t}; use servo_atoms::Atom; use servo_url::ServoUrl; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::HashMap; use std::default::Default; use std::ffi::CString; use std::hash::BuildHasherDefault; use std::mem; use std::ops::{Deref, DerefMut}; use std::ptr; use std::rc::Rc; #[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] pub enum CommonEventHandler { EventHandler(#[ignore_malloc_size_of = "Rc"] Rc), ErrorEventHandler(#[ignore_malloc_size_of = "Rc"] Rc), BeforeUnloadEventHandler(#[ignore_malloc_size_of = "Rc"] Rc), } impl CommonEventHandler { fn parent(&self) -> &CallbackFunction { match *self { CommonEventHandler::EventHandler(ref handler) => &handler.parent, CommonEventHandler::ErrorEventHandler(ref handler) => &handler.parent, CommonEventHandler::BeforeUnloadEventHandler(ref handler) => &handler.parent, } } } #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub enum ListenerPhase { Capturing, Bubbling, } /// #[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] struct InternalRawUncompiledHandler { source: DOMString, url: ServoUrl, line: usize, } /// A representation of an event handler, either compiled or uncompiled raw source, or null. #[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] enum InlineEventListener { Uncompiled(InternalRawUncompiledHandler), Compiled(CommonEventHandler), Null, } impl InlineEventListener { /// Get a compiled representation of this event handler, compiling it from its /// raw source if necessary. /// fn get_compiled_handler( &mut self, owner: &EventTarget, ty: &Atom, ) -> Option { match mem::replace(self, InlineEventListener::Null) { InlineEventListener::Null => None, InlineEventListener::Uncompiled(handler) => { let result = owner.get_compiled_event_handler(handler, ty); if let Some(ref compiled) = result { *self = InlineEventListener::Compiled(compiled.clone()); } result }, InlineEventListener::Compiled(handler) => { *self = InlineEventListener::Compiled(handler.clone()); Some(handler) }, } } } #[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] enum EventListenerType { Additive(#[ignore_malloc_size_of = "Rc"] Rc), Inline(InlineEventListener), } impl EventListenerType { fn get_compiled_listener( &mut self, owner: &EventTarget, ty: &Atom, ) -> Option { match self { &mut EventListenerType::Inline(ref mut inline) => inline .get_compiled_handler(owner, ty) .map(CompiledEventListener::Handler), &mut EventListenerType::Additive(ref listener) => { Some(CompiledEventListener::Listener(listener.clone())) }, } } } /// A representation of an EventListener/EventHandler object that has previously /// been compiled successfully, if applicable. pub enum CompiledEventListener { Listener(Rc), Handler(CommonEventHandler), } impl CompiledEventListener { #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm pub fn call_or_handle_event( &self, object: &T, event: &Event, exception_handle: ExceptionHandling, ) { // Step 3 match *self { CompiledEventListener::Listener(ref listener) => { let _ = listener.HandleEvent_(object, event, exception_handle); }, CompiledEventListener::Handler(ref handler) => { match *handler { CommonEventHandler::ErrorEventHandler(ref handler) => { if let Some(event) = event.downcast::() { let cx = object.global().get_cx(); rooted!(in(cx) let error = unsafe { event.Error(cx) }); let return_value = handler.Call_( object, EventOrString::String(event.Message()), Some(event.Filename()), Some(event.Lineno()), Some(event.Colno()), Some(error.handle()), exception_handle, ); // Step 4 if let Ok(return_value) = return_value { rooted!(in(cx) let return_value = return_value); if return_value.handle().is_boolean() && return_value.handle().to_boolean() == true { event.upcast::().PreventDefault(); } } return; } let _ = handler.Call_( object, EventOrString::Event(DomRoot::from_ref(event)), None, None, None, None, exception_handle, ); }, CommonEventHandler::BeforeUnloadEventHandler(ref handler) => { if let Some(event) = event.downcast::() { // Step 5 if let Ok(value) = handler.Call_(object, event.upcast::(), exception_handle) { let rv = event.ReturnValue(); if let Some(v) = value { if rv.is_empty() { event.SetReturnValue(v); } event.upcast::().PreventDefault(); } } } else { // Step 5, "Otherwise" clause let _ = handler.Call_(object, event.upcast::(), exception_handle); } }, CommonEventHandler::EventHandler(ref handler) => { if let Ok(value) = handler.Call_(object, event, exception_handle) { let cx = object.global().get_cx(); rooted!(in(cx) let value = value); let value = value.handle(); //Step 4 let should_cancel = match event.type_() { atom!("mouseover") => { value.is_boolean() && value.to_boolean() == true }, _ => value.is_boolean() && value.to_boolean() == false, }; if should_cancel { event.PreventDefault(); } } }, } }, } } } #[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf, PartialEq)] /// A listener in a collection of event listeners. struct EventListenerEntry { phase: ListenerPhase, listener: EventListenerType, } #[derive(JSTraceable, MallocSizeOf)] /// A mix of potentially uncompiled and compiled event listeners of the same type. struct EventListeners(Vec); impl Deref for EventListeners { type Target = Vec; fn deref(&self) -> &Vec { &self.0 } } impl DerefMut for EventListeners { fn deref_mut(&mut self) -> &mut Vec { &mut self.0 } } impl EventListeners { // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler fn get_inline_listener( &mut self, owner: &EventTarget, ty: &Atom, ) -> Option { for entry in &mut self.0 { if let EventListenerType::Inline(ref mut inline) = entry.listener { // Step 1.1-1.8 and Step 2 return inline.get_compiled_handler(owner, ty); } } // Step 2 None } // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler fn get_listeners( &mut self, phase: Option, owner: &EventTarget, ty: &Atom, ) -> Vec { self.0 .iter_mut() .filter_map(|entry| { if phase.is_none() || Some(entry.phase) == phase { // Step 1.1-1.8, 2 entry.listener.get_compiled_listener(owner, ty) } else { None } }) .collect() } fn has_listeners(&self) -> bool { // TODO: add, and take into account, a 'removed' field? // https://dom.spec.whatwg.org/#event-listener-removed self.0.len() > 0 } } #[dom_struct] pub struct EventTarget { reflector_: Reflector, handlers: DomRefCell>>, } impl EventTarget { pub fn new_inherited() -> EventTarget { EventTarget { reflector_: Reflector::new(), handlers: DomRefCell::new(Default::default()), } } fn new(global: &GlobalScope) -> DomRoot { reflect_dom_object(Box::new(EventTarget::new_inherited()), global, Wrap) } pub fn Constructor(global: &GlobalScope) -> Fallible> { Ok(EventTarget::new(global)) } pub fn has_listeners_for(&self, type_: &Atom) -> bool { match self.handlers.borrow().get(type_) { Some(listeners) => listeners.has_listeners(), None => false, } } pub fn get_listeners_for( &self, type_: &Atom, specific_phase: Option, ) -> Vec { self.handlers .borrow_mut() .get_mut(type_) .map_or(vec![], |listeners| { listeners.get_listeners(specific_phase, self, type_) }) } pub fn dispatch_event_with_target(&self, target: &EventTarget, event: &Event) -> EventStatus { if let Some(window) = target.global().downcast::() { if window.has_document() { assert!(window.Document().can_invoke_script()); } }; event.dispatch(self, Some(target)) } pub fn dispatch_event(&self, event: &Event) -> EventStatus { if let Some(window) = self.global().downcast::() { if window.has_document() { assert!(window.Document().can_invoke_script()); } }; event.dispatch(self, None) } pub fn remove_all_listeners(&self) { *self.handlers.borrow_mut() = Default::default(); } /// fn set_inline_event_listener(&self, ty: Atom, listener: Option) { let mut handlers = self.handlers.borrow_mut(); let entries = match handlers.entry(ty) { Occupied(entry) => entry.into_mut(), Vacant(entry) => entry.insert(EventListeners(vec![])), }; let idx = entries.iter().position(|ref entry| match entry.listener { EventListenerType::Inline(_) => true, _ => false, }); match idx { Some(idx) => { entries[idx].listener = EventListenerType::Inline(listener.unwrap_or(InlineEventListener::Null)); }, None => { if let Some(listener) = listener { entries.push(EventListenerEntry { phase: ListenerPhase::Bubbling, listener: EventListenerType::Inline(listener), }); } }, } } fn get_inline_event_listener(&self, ty: &Atom) -> Option { let mut handlers = self.handlers.borrow_mut(); handlers .get_mut(ty) .and_then(|entry| entry.get_inline_listener(self, ty)) } /// Store the raw uncompiled event handler for on-demand compilation later. /// pub fn set_event_handler_uncompiled( &self, url: ServoUrl, line: usize, ty: &str, source: DOMString, ) { let handler = InternalRawUncompiledHandler { source: source, line: line, url: url, }; self.set_inline_event_listener( Atom::from(ty), Some(InlineEventListener::Uncompiled(handler)), ); } // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler #[allow(unsafe_code)] fn get_compiled_event_handler( &self, handler: InternalRawUncompiledHandler, ty: &Atom, ) -> Option { // Step 1.1 let element = self.downcast::(); let document = match element { Some(element) => document_from_node(element), None => self.downcast::().unwrap().Document(), }; // Step 1.2 if !document.is_scripting_enabled() { return None; } // Step 1.3 let body: Vec = handler.source.encode_utf16().collect(); // TODO step 1.5 (form owner) // Step 1.6 let window = document.window(); let url_serialized = CString::new(handler.url.to_string()).unwrap(); let name = CString::new(&**ty).unwrap(); static mut ARG_NAMES: [*const c_char; 1] = [b"event\0" as *const u8 as *const c_char]; static mut ERROR_ARG_NAMES: [*const c_char; 5] = [ b"event\0" as *const u8 as *const c_char, b"source\0" as *const u8 as *const c_char, b"lineno\0" as *const u8 as *const c_char, b"colno\0" as *const u8 as *const c_char, b"error\0" as *const u8 as *const c_char, ]; // step 10 let is_error = ty == &atom!("error") && self.is::(); let args = unsafe { if is_error { &ERROR_ARG_NAMES[..] } else { &ARG_NAMES[..] } }; let cx = window.get_cx(); let options = CompileOptionsWrapper::new(cx, url_serialized.as_ptr(), handler.line as u32); // TODO step 1.10.1-3 (document, form owner, element in scope chain) let scopechain = AutoObjectVectorWrapper::new(cx); let _ac = JSAutoCompartment::new(cx, window.reflector().get_jsobject().get()); rooted!(in(cx) let mut handler = ptr::null_mut::()); let rv = unsafe { CompileFunction( cx, scopechain.ptr, options.ptr, name.as_ptr(), args.len() as u32, args.as_ptr(), body.as_ptr(), body.len() as size_t, handler.handle_mut().into(), ) }; if !rv || handler.get().is_null() { // Step 1.8.2 unsafe { let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get()); // FIXME(#13152): dispatch error event. report_pending_exception(cx, false); } // Step 1.8.1 / 1.8.3 return None; } // TODO step 1.11-13 let funobj = unsafe { JS_GetFunctionObject(handler.get()) }; assert!(!funobj.is_null()); // Step 1.14 if is_error { Some(CommonEventHandler::ErrorEventHandler(unsafe { OnErrorEventHandlerNonNull::new(cx, funobj) })) } else { if ty == &atom!("beforeunload") { Some(CommonEventHandler::BeforeUnloadEventHandler(unsafe { OnBeforeUnloadEventHandlerNonNull::new(cx, funobj) })) } else { Some(CommonEventHandler::EventHandler(unsafe { EventHandlerNonNull::new(cx, funobj) })) } } } #[allow(unsafe_code)] pub fn set_event_handler_common(&self, ty: &str, listener: Option>) where T: CallbackContainer, { let cx = self.global().get_cx(); let event_listener = listener.map(|listener| { InlineEventListener::Compiled(CommonEventHandler::EventHandler(unsafe { EventHandlerNonNull::new(cx, listener.callback()) })) }); self.set_inline_event_listener(Atom::from(ty), event_listener); } #[allow(unsafe_code)] pub fn set_error_event_handler(&self, ty: &str, listener: Option>) where T: CallbackContainer, { let cx = self.global().get_cx(); let event_listener = listener.map(|listener| { InlineEventListener::Compiled(CommonEventHandler::ErrorEventHandler(unsafe { OnErrorEventHandlerNonNull::new(cx, listener.callback()) })) }); self.set_inline_event_listener(Atom::from(ty), event_listener); } #[allow(unsafe_code)] pub fn set_beforeunload_event_handler( &self, ty: &str, listener: Option>, ) where T: CallbackContainer, { let cx = self.global().get_cx(); let event_listener = listener.map(|listener| { InlineEventListener::Compiled(CommonEventHandler::BeforeUnloadEventHandler(unsafe { OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback()) })) }); self.set_inline_event_listener(Atom::from(ty), event_listener); } #[allow(unsafe_code)] pub fn get_event_handler_common(&self, ty: &str) -> Option> { let cx = self.global().get_cx(); let listener = self.get_inline_event_listener(&Atom::from(ty)); unsafe { listener.map(|listener| { CallbackContainer::new(cx, listener.parent().callback_holder().get()) }) } } pub fn has_handlers(&self) -> bool { !self.handlers.borrow().is_empty() } // https://dom.spec.whatwg.org/#concept-event-fire pub fn fire_event(&self, name: Atom) -> DomRoot { self.fire_event_with_params( name, EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, ) } // https://dom.spec.whatwg.org/#concept-event-fire pub fn fire_bubbling_event(&self, name: Atom) -> DomRoot { self.fire_event_with_params(name, EventBubbles::Bubbles, EventCancelable::NotCancelable) } // https://dom.spec.whatwg.org/#concept-event-fire pub fn fire_cancelable_event(&self, name: Atom) -> DomRoot { self.fire_event_with_params( name, EventBubbles::DoesNotBubble, EventCancelable::Cancelable, ) } // https://dom.spec.whatwg.org/#concept-event-fire pub fn fire_bubbling_cancelable_event(&self, name: Atom) -> DomRoot { self.fire_event_with_params(name, EventBubbles::Bubbles, EventCancelable::Cancelable) } // https://dom.spec.whatwg.org/#concept-event-fire pub fn fire_event_with_params( &self, name: Atom, bubbles: EventBubbles, cancelable: EventCancelable, ) -> DomRoot { let event = Event::new(&self.global(), name, bubbles, cancelable); event.fire(self); event } // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener pub fn add_event_listener( &self, ty: DOMString, listener: Option>, options: AddEventListenerOptions, ) { let listener = match listener { Some(l) => l, None => return, }; let mut handlers = self.handlers.borrow_mut(); let entry = match handlers.entry(Atom::from(ty)) { Occupied(entry) => entry.into_mut(), Vacant(entry) => entry.insert(EventListeners(vec![])), }; let phase = if options.parent.capture { ListenerPhase::Capturing } else { ListenerPhase::Bubbling }; let new_entry = EventListenerEntry { phase: phase, listener: EventListenerType::Additive(listener), }; if !entry.contains(&new_entry) { entry.push(new_entry); } } // https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener pub fn remove_event_listener( &self, ty: DOMString, listener: Option>, options: EventListenerOptions, ) { let ref listener = match listener { Some(l) => l, None => return, }; let mut handlers = self.handlers.borrow_mut(); let entry = handlers.get_mut(&Atom::from(ty)); for entry in entry { let phase = if options.capture { ListenerPhase::Capturing } else { ListenerPhase::Bubbling }; let old_entry = EventListenerEntry { phase: phase, listener: EventListenerType::Additive(listener.clone()), }; if let Some(position) = entry.iter().position(|e| *e == old_entry) { entry.remove(position); } } } } impl EventTargetMethods for EventTarget { // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener fn AddEventListener( &self, ty: DOMString, listener: Option>, options: AddEventListenerOptionsOrBoolean, ) { self.add_event_listener(ty, listener, options.into()) } // https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener fn RemoveEventListener( &self, ty: DOMString, listener: Option>, options: EventListenerOptionsOrBoolean, ) { self.remove_event_listener(ty, listener, options.into()) } // https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent fn DispatchEvent(&self, event: &Event) -> Fallible { if event.dispatching() || !event.initialized() { return Err(Error::InvalidState); } event.set_trusted(false); Ok(match self.dispatch_event(event) { EventStatus::Canceled => false, EventStatus::NotCanceled => true, }) } } impl VirtualMethods for EventTarget { fn super_type(&self) -> Option<&dyn VirtualMethods> { None } } impl From for AddEventListenerOptions { fn from(options: AddEventListenerOptionsOrBoolean) -> Self { match options { AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options, AddEventListenerOptionsOrBoolean::Boolean(capture) => Self { parent: EventListenerOptions { capture }, }, } } } impl From for EventListenerOptions { fn from(options: EventListenerOptionsOrBoolean) -> Self { match options { EventListenerOptionsOrBoolean::EventListenerOptions(options) => options, EventListenerOptionsOrBoolean::Boolean(capture) => Self { capture }, } } }