Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). Either: - [ ] There are tests for these changes OR - [X] These changes do not require tests because no functional change Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. Source-Repo: https://github.com/servo/servo Source-Revision: ea84601bf08618254200b3faca055c36e9ff29b4
564 lines
22 KiB
Rust
564 lines
22 KiB
Rust
/* 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, ExceptionHandling, CallbackFunction};
|
|
use dom::bindings::cell::DOMRefCell;
|
|
use dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods;
|
|
use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
|
|
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
|
|
use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
|
|
use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
|
|
use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
|
|
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
|
use dom::bindings::codegen::UnionTypes::EventOrString;
|
|
use dom::bindings::error::{Error, Fallible, report_pending_exception};
|
|
use dom::bindings::inheritance::{Castable, EventTargetTypeId};
|
|
use dom::bindings::js::Root;
|
|
use dom::bindings::reflector::{Reflectable, Reflector};
|
|
use dom::element::Element;
|
|
use dom::errorevent::ErrorEvent;
|
|
use dom::event::{Event, EventBubbles, EventCancelable};
|
|
use dom::eventdispatcher::dispatch_event;
|
|
use dom::node::document_from_node;
|
|
use dom::virtualmethods::VirtualMethods;
|
|
use dom::window::Window;
|
|
use fnv::FnvHasher;
|
|
use heapsize::HeapSizeOf;
|
|
use js::jsapi::{CompileFunction, JS_GetFunctionObject, RootedValue, RootedFunction, JSAutoCompartment};
|
|
use js::rust::{AutoObjectVectorWrapper, CompileOptionsWrapper};
|
|
use libc::{c_char, size_t};
|
|
use std::collections::HashMap;
|
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
|
use std::default::Default;
|
|
use std::ffi::CString;
|
|
use std::hash::BuildHasherDefault;
|
|
use std::mem;
|
|
use std::ops::{Deref, DerefMut};
|
|
use std::rc::Rc;
|
|
use std::{intrinsics, ptr};
|
|
use string_cache::Atom;
|
|
use url::Url;
|
|
use util::str::DOMString;
|
|
|
|
#[derive(PartialEq, Clone, JSTraceable)]
|
|
pub enum CommonEventHandler {
|
|
EventHandler(Rc<EventHandlerNonNull>),
|
|
ErrorEventHandler(Rc<OnErrorEventHandlerNonNull>),
|
|
}
|
|
|
|
impl CommonEventHandler {
|
|
fn parent(&self) -> &CallbackFunction {
|
|
match *self {
|
|
CommonEventHandler::EventHandler(ref handler) => &handler.parent,
|
|
CommonEventHandler::ErrorEventHandler(ref handler) => &handler.parent,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(JSTraceable, Copy, Clone, PartialEq, HeapSizeOf)]
|
|
pub enum ListenerPhase {
|
|
Capturing,
|
|
Bubbling,
|
|
}
|
|
|
|
impl PartialEq for EventTargetTypeId {
|
|
#[inline]
|
|
fn eq(&self, other: &EventTargetTypeId) -> bool {
|
|
match (*self, *other) {
|
|
(EventTargetTypeId::Node(this_type), EventTargetTypeId::Node(other_type)) => {
|
|
this_type == other_type
|
|
}
|
|
_ => self.eq_slow(other)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EventTargetTypeId {
|
|
#[allow(unsafe_code)]
|
|
fn eq_slow(&self, other: &EventTargetTypeId) -> bool {
|
|
match (*self, *other) {
|
|
(EventTargetTypeId::Node(this_type), EventTargetTypeId::Node(other_type)) => {
|
|
this_type == other_type
|
|
}
|
|
(EventTargetTypeId::WorkerGlobalScope(this_type),
|
|
EventTargetTypeId::WorkerGlobalScope(other_type)) => {
|
|
this_type == other_type
|
|
}
|
|
(EventTargetTypeId::XMLHttpRequestEventTarget(this_type),
|
|
EventTargetTypeId::XMLHttpRequestEventTarget(other_type)) => {
|
|
this_type == other_type
|
|
}
|
|
(_, _) => {
|
|
unsafe {
|
|
intrinsics::discriminant_value(self) == intrinsics::discriminant_value(other)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// https://html.spec.whatwg.org/multipage/#internal-raw-uncompiled-handler
|
|
#[derive(JSTraceable, Clone, PartialEq)]
|
|
pub struct InternalRawUncompiledHandler {
|
|
source: DOMString,
|
|
url: Url,
|
|
line: usize,
|
|
}
|
|
|
|
/// A representation of an event handler, either compiled or uncompiled raw source, or null.
|
|
#[derive(JSTraceable, PartialEq, Clone)]
|
|
pub 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.
|
|
/// https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler
|
|
fn get_compiled_handler(&mut self, owner: &EventTarget, ty: &Atom)
|
|
-> Option<CommonEventHandler> {
|
|
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(JSTraceable, Clone, PartialEq)]
|
|
enum EventListenerType {
|
|
Additive(Rc<EventListener>),
|
|
Inline(InlineEventListener),
|
|
}
|
|
|
|
impl HeapSizeOf for EventListenerType {
|
|
fn heap_size_of_children(&self) -> usize {
|
|
// FIXME: Rc<T> isn't HeapSizeOf and we can't ignore it due to #6870 and #6871
|
|
0
|
|
}
|
|
}
|
|
|
|
impl EventListenerType {
|
|
fn get_compiled_listener(&mut self, owner: &EventTarget, ty: &Atom)
|
|
-> Option<CompiledEventListener> {
|
|
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<EventListener>),
|
|
Handler(CommonEventHandler),
|
|
}
|
|
|
|
impl CompiledEventListener {
|
|
// https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm
|
|
pub fn call_or_handle_event<T: Reflectable>(&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::<ErrorEvent>() {
|
|
let global = object.global();
|
|
let cx = global.r().get_cx();
|
|
let error = RootedValue::new(cx, 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 {
|
|
let return_value = RootedValue::new(cx, return_value);
|
|
if return_value.handle().is_boolean() && return_value.handle().to_boolean() == true {
|
|
event.upcast::<Event>().PreventDefault();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
let _ = handler.Call_(object, EventOrString::Event(Root::from_ref(event)),
|
|
None, None, None, None, exception_handle);
|
|
}
|
|
|
|
CommonEventHandler::EventHandler(ref handler) => {
|
|
if let Ok(value) = handler.Call_(object, event, exception_handle) {
|
|
let global = object.global();
|
|
let cx = global.r().get_cx();
|
|
let value = RootedValue::new(cx, value);
|
|
let value = value.handle();
|
|
|
|
//Step 4
|
|
let should_cancel = match event.type_() {
|
|
atom!("mouseover") => value.is_boolean() && value.to_boolean() == true,
|
|
atom!("beforeunload") => value.is_null(),
|
|
_ => value.is_boolean() && value.to_boolean() == false
|
|
};
|
|
if should_cancel {
|
|
event.PreventDefault();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(JSTraceable, Clone, PartialEq, HeapSizeOf)]
|
|
#[privatize]
|
|
/// A listener in a collection of event listeners.
|
|
struct EventListenerEntry {
|
|
phase: ListenerPhase,
|
|
listener: EventListenerType
|
|
}
|
|
|
|
#[derive(JSTraceable, HeapSizeOf)]
|
|
/// A mix of potentially uncompiled and compiled event listeners of the same type.
|
|
struct EventListeners(Vec<EventListenerEntry>);
|
|
|
|
impl Deref for EventListeners {
|
|
type Target = Vec<EventListenerEntry>;
|
|
fn deref(&self) -> &Vec<EventListenerEntry> {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for EventListeners {
|
|
fn deref_mut(&mut self) -> &mut Vec<EventListenerEntry> {
|
|
&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<CommonEventHandler> {
|
|
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<ListenerPhase>, owner: &EventTarget, ty: &Atom)
|
|
-> Vec<CompiledEventListener> {
|
|
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()
|
|
}
|
|
}
|
|
|
|
#[dom_struct]
|
|
pub struct EventTarget {
|
|
reflector_: Reflector,
|
|
handlers: DOMRefCell<HashMap<Atom, EventListeners, BuildHasherDefault<FnvHasher>>>,
|
|
}
|
|
|
|
impl EventTarget {
|
|
pub fn new_inherited() -> EventTarget {
|
|
EventTarget {
|
|
reflector_: Reflector::new(),
|
|
handlers: DOMRefCell::new(Default::default()),
|
|
}
|
|
}
|
|
|
|
pub fn get_listeners_for(&self,
|
|
type_: &Atom,
|
|
specific_phase: Option<ListenerPhase>)
|
|
-> Vec<CompiledEventListener> {
|
|
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) -> bool {
|
|
dispatch_event(self, Some(target), event)
|
|
}
|
|
|
|
pub fn dispatch_event(&self, event: &Event) -> bool {
|
|
dispatch_event(self, None, event)
|
|
}
|
|
|
|
/// https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handlers-11
|
|
pub fn set_inline_event_listener(&self,
|
|
ty: Atom,
|
|
listener: Option<InlineEventListener>) {
|
|
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<CommonEventHandler> {
|
|
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.
|
|
/// https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3
|
|
pub fn set_event_handler_uncompiled(&self,
|
|
url: Url,
|
|
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)]
|
|
pub fn get_compiled_event_handler(&self,
|
|
handler: InternalRawUncompiledHandler,
|
|
ty: &Atom)
|
|
-> Option<CommonEventHandler> {
|
|
// Step 1.1
|
|
let element = self.downcast::<Element>();
|
|
let document = match element {
|
|
Some(element) => document_from_node(element),
|
|
None => self.downcast::<Window>().unwrap().Document(),
|
|
};
|
|
|
|
// TODO step 1.2 (browsing context/scripting enabled)
|
|
|
|
// Step 1.3
|
|
let body: Vec<u16> = 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::<Window>();
|
|
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());
|
|
let mut handler = RootedFunction::new(cx, 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())
|
|
};
|
|
if !rv || handler.ptr.is_null() {
|
|
// Step 1.8.2
|
|
report_pending_exception(cx, self.reflector().get_jsobject().get());
|
|
// Step 1.8.1 / 1.8.3
|
|
return None;
|
|
}
|
|
|
|
// TODO step 1.11-13
|
|
let funobj = unsafe { JS_GetFunctionObject(handler.ptr) };
|
|
assert!(!funobj.is_null());
|
|
// Step 1.14
|
|
if is_error {
|
|
Some(CommonEventHandler::ErrorEventHandler(OnErrorEventHandlerNonNull::new(funobj)))
|
|
} else {
|
|
Some(CommonEventHandler::EventHandler(EventHandlerNonNull::new(funobj)))
|
|
}
|
|
}
|
|
|
|
pub fn set_event_handler_common<T: CallbackContainer>(
|
|
&self, ty: &str, listener: Option<Rc<T>>)
|
|
{
|
|
let event_listener = listener.map(|listener|
|
|
InlineEventListener::Compiled(
|
|
CommonEventHandler::EventHandler(
|
|
EventHandlerNonNull::new(listener.callback()))));
|
|
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
|
}
|
|
|
|
pub fn set_error_event_handler<T: CallbackContainer>(
|
|
&self, ty: &str, listener: Option<Rc<T>>)
|
|
{
|
|
let event_listener = listener.map(|listener|
|
|
InlineEventListener::Compiled(
|
|
CommonEventHandler::ErrorEventHandler(
|
|
OnErrorEventHandlerNonNull::new(listener.callback()))));
|
|
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
|
}
|
|
|
|
pub fn get_event_handler_common<T: CallbackContainer>(&self, ty: &str) -> Option<Rc<T>> {
|
|
let listener = self.get_inline_event_listener(&Atom::from(ty));
|
|
listener.map(|listener| CallbackContainer::new(listener.parent().callback()))
|
|
}
|
|
|
|
pub fn has_handlers(&self) -> bool {
|
|
!self.handlers.borrow().is_empty()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#fire-a-simple-event
|
|
pub fn fire_simple_event(&self, name: &str) -> Root<Event> {
|
|
self.fire_event(name, EventBubbles::DoesNotBubble,
|
|
EventCancelable::NotCancelable)
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#concept-event-fire
|
|
pub fn fire_event(&self, name: &str,
|
|
bubbles: EventBubbles,
|
|
cancelable: EventCancelable)
|
|
-> Root<Event> {
|
|
let global = self.global();
|
|
let event = Event::new(global.r(), Atom::from(name), bubbles, cancelable);
|
|
|
|
event.fire(self);
|
|
|
|
event
|
|
}
|
|
}
|
|
|
|
impl EventTargetMethods for EventTarget {
|
|
// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
|
|
fn AddEventListener(&self,
|
|
ty: DOMString,
|
|
listener: Option<Rc<EventListener>>,
|
|
capture: bool) {
|
|
if let Some(listener) = listener {
|
|
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 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
|
|
fn RemoveEventListener(&self,
|
|
ty: DOMString,
|
|
listener: Option<Rc<EventListener>>,
|
|
capture: bool) {
|
|
if let Some(ref listener) = listener {
|
|
let mut handlers = self.handlers.borrow_mut();
|
|
let entry = handlers.get_mut(&Atom::from(ty));
|
|
for entry in entry {
|
|
let phase = if 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent
|
|
fn DispatchEvent(&self, event: &Event) -> Fallible<bool> {
|
|
if event.dispatching() || !event.initialized() {
|
|
return Err(Error::InvalidState);
|
|
}
|
|
event.set_trusted(false);
|
|
Ok(self.dispatch_event(event))
|
|
}
|
|
}
|
|
|
|
impl VirtualMethods for EventTarget {
|
|
fn super_type(&self) -> Option<&VirtualMethods> {
|
|
None
|
|
}
|
|
}
|