diff --git a/src/components/script/dom/bindings/codegen/Bindings.conf b/src/components/script/dom/bindings/codegen/Bindings.conf index b2467b6d647..b7d728021d1 100644 --- a/src/components/script/dom/bindings/codegen/Bindings.conf +++ b/src/components/script/dom/bindings/codegen/Bindings.conf @@ -34,6 +34,7 @@ DOMInterfaces = { 'DOMParser': {}, 'Element': {}, 'Event': {}, +'EventHandler': {}, 'EventListener': { 'nativeType': 'EventListenerBinding::EventListener', }, diff --git a/src/components/script/dom/bindings/codegen/CodegenRust.py b/src/components/script/dom/bindings/codegen/CodegenRust.py index a749bbce65e..9fca81cd287 100644 --- a/src/components/script/dom/bindings/codegen/CodegenRust.py +++ b/src/components/script/dom/bindings/codegen/CodegenRust.py @@ -705,17 +705,17 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, "rooting issues") # XXXbz we're going to assume that callback types are always # nullable and always have [TreatNonCallableAsNull] for now. - haveCallable = "${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())" + haveCallable = "${val}.is_object() && JS_ObjectIsCallable(cx, ${val}.to_object()) != 0" if defaultValue is not None: assert(isinstance(defaultValue, IDLNullValue)) haveCallable = "${haveValue} && " + haveCallable return ( "if (%s) {\n" - " ${declName} = &${val}.toObject();\n" + " ${val}.to_object()\n" "} else {\n" - " ${declName} = NULL;\n" + " ptr::mut_null()\n" "}" % haveCallable, - CGGeneric("JSObject*"), None, needsRooting) + CGGeneric("*mut JSObject"), needsRooting) if type.isAny(): assert not isEnforceRange and not isClamp @@ -1346,6 +1346,14 @@ class CGIfWrapper(CGWrapper): CGWrapper.__init__(self, CGIndenter(child), pre=pre.define(), post="\n}") +class CGTemplatedType(CGWrapper): + def __init__(self, templateName, child, isConst=False, isReference=False): + const = "const " if isConst else "" + pre = "%s%s<" % (const, templateName) + ref = "&" if isReference else "" + post = ">%s" % ref + CGWrapper.__init__(self, child, pre=pre, post=post) + class CGNamespace(CGWrapper): def __init__(self, namespace, child, public=False): pre = "%smod %s {\n" % ("pub " if public else "", namespace) @@ -3128,7 +3136,7 @@ class ClassMember(ClassItem): ClassItem.__init__(self, name, visibility) def declare(self, cgClass): - return '%s: %s,\n' % (self.name, self.type) + return '%s %s: %s,\n' % (self.visibility, self.name, self.type) def define(self, cgClass): if not self.static: @@ -4357,7 +4365,7 @@ class CGNativeMember(ClassMethod): elif type.isAny(): typeDecl, template = "JS::Value", "return ${declName};" elif type.isObject(): - typeDecl, template = "JSObject*", "return ${declName};" + typeDecl, template = "*JSObject", "return ${declName};" elif type.isSpiderMonkeyInterface(): if type.nullable(): returnCode = "return ${declName}.IsNull() ? nullptr : ${declName}.Value().Obj();" @@ -4561,7 +4569,7 @@ class CGNativeMember(ClassMethod): elif optional: # Note: All variadic args claim to be optional, but we can just use # empty arrays to represent them not being present. - decl = CGTemplatedType("Optional", decl) + decl = CGTemplatedType("Option", decl) ref = True return (decl, ref) @@ -4816,6 +4824,7 @@ class CallbackMember(CGNativeMember): def getResultConversion(self): replacements = { "val": "rval", + "declName": "rvalDecl", } if isJSImplementedDescriptor(self.descriptorProvider): diff --git a/src/components/script/dom/bindings/conversions.rs b/src/components/script/dom/bindings/conversions.rs index 11e937bd261..e1a155fa457 100644 --- a/src/components/script/dom/bindings/conversions.rs +++ b/src/components/script/dom/bindings/conversions.rs @@ -9,7 +9,7 @@ use dom::bindings::utils::jsstring_to_str; use dom::bindings::utils::unwrap_jsmanaged; use servo_util::str::DOMString; -use js::jsapi::{JSBool, JSContext}; +use js::jsapi::{JSBool, JSContext, JSObject}; use js::jsapi::{JS_ValueToUint64, JS_ValueToInt64}; use js::jsapi::{JS_ValueToECMAUint32, JS_ValueToECMAInt32}; use js::jsapi::{JS_ValueToUint16, JS_ValueToNumber, JS_ValueToBoolean}; @@ -18,7 +18,7 @@ use js::jsapi::{JS_NewUCStringCopyN, JS_NewStringCopyN}; use js::jsapi::{JS_WrapValue}; use js::jsval::JSVal; use js::jsval::{UndefinedValue, NullValue, BooleanValue, Int32Value, UInt32Value}; -use js::jsval::{StringValue, ObjectValue}; +use js::jsval::{StringValue, ObjectValue, ObjectOrNullValue}; use js::glue::RUST_JS_NumberValue; use libc; use std::default::Default; @@ -348,3 +348,9 @@ impl> FromJSValConvertible<()> for Option } } } + +impl ToJSValConvertible for *mut JSObject { + fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { + ObjectOrNullValue(*self) + } +} diff --git a/src/components/script/dom/document.rs b/src/components/script/dom/document.rs index 4c39752f985..68ea97d1335 100644 --- a/src/components/script/dom/document.rs +++ b/src/components/script/dom/document.rs @@ -5,6 +5,7 @@ use dom::bindings::codegen::InheritTypes::{DocumentDerived, EventCast, HTMLElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLHeadElementCast, TextCast, ElementCast}; use dom::bindings::codegen::InheritTypes::{DocumentTypeCast, HTMLHtmlElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::EventTargetCast; use dom::bindings::codegen::BindingDeclarations::DocumentBinding; use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable, TemporaryPushable}; use dom::bindings::js::OptionalRootable; @@ -21,7 +22,7 @@ use dom::element::{Element, AttributeHandlers, get_attribute_parts}; use dom::element::{HTMLHtmlElementTypeId, HTMLHeadElementTypeId, HTMLTitleElementTypeId}; use dom::element::{HTMLBodyElementTypeId, HTMLFrameSetElementTypeId}; use dom::event::Event; -use dom::eventtarget::{EventTarget, NodeTargetTypeId}; +use dom::eventtarget::{EventTarget, NodeTargetTypeId, EventTargetHelpers}; use dom::htmlcollection::{HTMLCollection, CollectionFilter}; use dom::htmlelement::HTMLElement; use dom::htmlheadelement::HTMLHeadElement; @@ -44,7 +45,7 @@ use servo_util::namespace::{Namespace, Null}; use servo_util::str::{DOMString, null_str_as_empty_ref}; use collections::hashmap::HashMap; -use js::jsapi::JSContext; +use js::jsapi::{JSObject, JSContext}; use std::ascii::StrAsciiExt; use url::{Url, from_str}; @@ -325,6 +326,8 @@ pub trait DocumentMethods { fn Applets(&self) -> Temporary; fn Location(&mut self) -> Temporary; fn Children(&self) -> Temporary; + fn GetOnload(&self, _cx: *mut JSContext) -> *mut JSObject; + fn SetOnload(&mut self, _cx: *mut JSContext, listener: *mut JSObject); } impl<'a> DocumentMethods for JSRef<'a, Document> { @@ -804,4 +807,14 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { let window = self.window.root(); HTMLCollection::children(&*window, NodeCast::from_ref(self)) } + + fn GetOnload(&self, _cx: *mut JSContext) -> *mut JSObject { + let eventtarget: &JSRef = EventTargetCast::from_ref(self); + eventtarget.get_event_handler_common("load") + } + + fn SetOnload(&mut self, _cx: *mut JSContext, listener: *mut JSObject) { + let eventtarget: &mut JSRef = EventTargetCast::from_mut_ref(self); + eventtarget.set_event_handler_common("load", listener) + } } diff --git a/src/components/script/dom/eventtarget.rs b/src/components/script/dom/eventtarget.rs index e1b8def7357..b348dec122f 100644 --- a/src/components/script/dom/eventtarget.rs +++ b/src/components/script/dom/eventtarget.rs @@ -2,16 +2,19 @@ * 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::BindingDeclarations::EventListenerBinding::EventListener; +use dom::bindings::error::{Fallible, InvalidState}; use dom::bindings::js::JSRef; use dom::bindings::utils::{Reflectable, Reflector}; -use dom::bindings::error::{Fallible, InvalidState}; -use dom::bindings::codegen::BindingDeclarations::EventListenerBinding::EventListener; use dom::event::Event; use dom::eventdispatcher::dispatch_event; use dom::node::NodeTypeId; use dom::xmlhttprequest::XMLHttpRequestId; use dom::virtualmethods::VirtualMethods; +use js::jsapi::JSObject; use servo_util::str::DOMString; +use std::ptr; use collections::hashmap::HashMap; @@ -28,10 +31,24 @@ pub enum EventTargetTypeId { XMLHttpRequestTargetTypeId(XMLHttpRequestId) } +#[deriving(Eq, Encodable)] +pub enum EventListenerType { + Additive(EventListener), + Inline(EventListener), +} + +impl EventListenerType { + fn get_listener(&self) -> EventListener { + match *self { + Additive(listener) | Inline(listener) => listener + } + } +} + #[deriving(Eq,Encodable)] pub struct EventListenerEntry { pub phase: ListenerPhase, - pub listener: EventListener + pub listener: EventListenerType } #[deriving(Encodable)] @@ -52,7 +69,7 @@ impl EventTarget { pub fn get_listeners(&self, type_: &str) -> Option> { self.handlers.find_equiv(&type_).map(|listeners| { - listeners.iter().map(|entry| entry.listener).collect() + listeners.iter().map(|entry| entry.listener.get_listener()).collect() }) } @@ -60,7 +77,7 @@ impl EventTarget { -> Option> { self.handlers.find_equiv(&type_).map(|listeners| { let filtered = listeners.iter().filter(|entry| entry.phase == desired_phase); - filtered.map(|entry| entry.listener).collect() + filtered.map(|entry| entry.listener.get_listener()).collect() }) } } @@ -69,6 +86,12 @@ pub trait EventTargetHelpers { fn dispatch_event_with_target<'a>(&self, target: Option>, event: &mut JSRef) -> Fallible; + fn set_inline_event_listener(&mut self, + ty: DOMString, + listener: Option); + fn get_inline_event_listener(&self, ty: DOMString) -> Option; + fn set_event_handler_common(&mut self, ty: &str, listener: *mut JSObject); + fn get_event_handler_common(&self, ty: &str) -> *mut JSObject; } impl<'a> EventTargetHelpers for JSRef<'a, EventTarget> { @@ -80,6 +103,57 @@ impl<'a> EventTargetHelpers for JSRef<'a, EventTarget> { } Ok(dispatch_event(self, target, event)) } + + fn set_inline_event_listener(&mut self, + ty: DOMString, + listener: Option) { + let entries = self.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: Capturing, //XXXjdm no idea when inline handlers should run + listener: Inline(listener.unwrap()), + }); + } + } + } + } + + fn get_inline_event_listener(&self, ty: DOMString) -> Option { + let entries = self.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_common(&mut self, ty: &str, listener: *mut JSObject) { + let listener = EventListener::new(listener); + self.set_inline_event_listener(ty.to_owned(), Some(listener)); + } + + fn get_event_handler_common(&self, ty: &str) -> *mut JSObject { + let listener = self.get_inline_event_listener(ty.to_owned()); + listener.map(|listener| listener.parent.callback()).unwrap_or(ptr::mut_null()) + } } pub trait EventTargetMethods { @@ -104,7 +178,7 @@ impl<'a> EventTargetMethods for JSRef<'a, EventTarget> { let phase = if capture { Capturing } else { Bubbling }; let new_entry = EventListenerEntry { phase: phase, - listener: listener + listener: Additive(listener) }; if entry.as_slice().position_elem(&new_entry).is_none() { entry.push(new_entry); @@ -122,7 +196,7 @@ impl<'a> EventTargetMethods for JSRef<'a, EventTarget> { let phase = if capture { Capturing } else { Bubbling }; let old_entry = EventListenerEntry { phase: phase, - listener: listener + listener: Additive(listener) }; let position = entry.as_slice().position_elem(&old_entry); for &position in position.iter() { diff --git a/src/components/script/dom/webidls/Document.webidl b/src/components/script/dom/webidls/Document.webidl index d89309f1c03..232beb1bf77 100644 --- a/src/components/script/dom/webidls/Document.webidl +++ b/src/components/script/dom/webidls/Document.webidl @@ -68,3 +68,4 @@ partial interface Document { }; Document implements ParentNode; +Document implements GlobalEventHandlers; diff --git a/src/components/script/dom/webidls/EventHandler.webidl b/src/components/script/dom/webidls/EventHandler.webidl new file mode 100644 index 00000000000..ee760bcd05b --- /dev/null +++ b/src/components/script/dom/webidls/EventHandler.webidl @@ -0,0 +1,45 @@ +/* -*- 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 + * http://www.whatwg.org/specs/web-apps/current-work/#eventhandler + * + * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and + * Opera Software ASA. You are granted a license to use, reproduce + * and create derivative works of this document. + */ + +//[TreatNonObjectAsNull] //XXXjdm webidl.py assertion +callback EventHandlerNonNull = any (Event event); +typedef EventHandlerNonNull? EventHandler; + +//[TreatNonObjectAsNull] //XXXjdm webidl.py assertion +callback OnErrorEventHandlerNonNull = boolean ((Event or DOMString) event, optional DOMString source, optional unsigned long lineno, optional unsigned long column, optional any error); +typedef OnErrorEventHandlerNonNull? OnErrorEventHandler; + +[NoInterfaceObject] +interface GlobalEventHandlers { + attribute EventHandler onload; +}; + +[NoInterfaceObject] +interface WindowEventHandlers { + attribute EventHandler onunload; +}; + +// The spec has |attribute OnErrorEventHandler onerror;| on +// GlobalEventHandlers, and calls the handler differently depending on +// whether an ErrorEvent was fired. We don't do that, and until we do we'll +// need to distinguish between onerror on Window or on nodes. + +[NoInterfaceObject] +interface OnErrorEventHandlerForNodes { + attribute EventHandler onerror; +}; + +[NoInterfaceObject] +interface OnErrorEventHandlerForWindow { + attribute OnErrorEventHandler onerror; +}; diff --git a/src/components/script/dom/webidls/Window.webidl b/src/components/script/dom/webidls/Window.webidl index ad18ff9998e..f738f37ddee 100644 --- a/src/components/script/dom/webidls/Window.webidl +++ b/src/components/script/dom/webidls/Window.webidl @@ -84,6 +84,9 @@ interface WindowTimers { void clearInterval(optional long handle = 0);*/ }; Window implements WindowTimers; +Window implements GlobalEventHandlers; +Window implements WindowEventHandlers; +Window implements OnErrorEventHandlerForWindow; // Proprietary extensions. partial interface Window { diff --git a/src/components/script/dom/window.rs b/src/components/script/dom/window.rs index 42804033815..ecb08928832 100644 --- a/src/components/script/dom/window.rs +++ b/src/components/script/dom/window.rs @@ -3,13 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::BindingDeclarations::WindowBinding; +use dom::bindings::codegen::InheritTypes::EventTargetCast; use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable}; use dom::bindings::trace::{Traceable, Untraceable}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::browsercontext::BrowserContext; use dom::document::Document; use dom::element::Element; -use dom::eventtarget::{EventTarget, WindowTypeId}; +use dom::eventtarget::{EventTarget, WindowTypeId, EventTargetHelpers}; use dom::console::Console; use dom::location::Location; use dom::navigator::Navigator; @@ -23,7 +24,7 @@ use servo_util::str::DOMString; use servo_util::task::{spawn_named}; use servo_util::url::parse_url; -use js::jsapi::JSContext; +use js::jsapi::{JSContext, JSObject}; use js::jsapi::{JS_GC, JS_GetRuntime}; use js::jsval::{NullValue, JSVal}; @@ -140,6 +141,12 @@ pub trait WindowMethods { fn Window(&self) -> Temporary; fn Self(&self) -> Temporary; fn Performance(&mut self) -> Temporary; + fn GetOnload(&self, _cx: *mut JSContext) -> *mut JSObject; + fn SetOnload(&mut self, _cx: *mut JSContext, listener: *mut JSObject); + fn GetOnunload(&self, _cx: *mut JSContext) -> *mut JSObject; + fn SetOnunload(&mut self, _cx: *mut JSContext, listener: *mut JSObject); + fn GetOnerror(&self, _cx: *mut JSContext) -> *mut JSObject; + fn SetOnerror(&mut self, _cx: *mut JSContext, listener: *mut JSObject); fn Debug(&self, message: DOMString); fn Gc(&self); } @@ -268,6 +275,36 @@ impl<'a> WindowMethods for JSRef<'a, Window> { Temporary::new(self.performance.get_ref().clone()) } + fn GetOnload(&self, _cx: *mut JSContext) -> *mut JSObject { + let eventtarget: &JSRef = EventTargetCast::from_ref(self); + eventtarget.get_event_handler_common("load") + } + + fn SetOnload(&mut self, _cx: *mut JSContext, listener: *mut JSObject) { + let eventtarget: &mut JSRef = EventTargetCast::from_mut_ref(self); + eventtarget.set_event_handler_common("load", listener) + } + + fn GetOnunload(&self, _cx: *mut JSContext) -> *mut JSObject { + let eventtarget: &JSRef = EventTargetCast::from_ref(self); + eventtarget.get_event_handler_common("unload") + } + + fn SetOnunload(&mut self, _cx: *mut JSContext, listener: *mut JSObject) { + let eventtarget: &mut JSRef = EventTargetCast::from_mut_ref(self); + eventtarget.set_event_handler_common("unload", listener) + } + + fn GetOnerror(&self, _cx: *mut JSContext) -> *mut JSObject { + let eventtarget: &JSRef = EventTargetCast::from_ref(self); + eventtarget.get_event_handler_common("error") + } + + fn SetOnerror(&mut self, _cx: *mut JSContext, listener: *mut JSObject) { + let eventtarget: &mut JSRef = EventTargetCast::from_mut_ref(self); + eventtarget.set_event_handler_common("error", listener) + } + fn Debug(&self, message: DOMString) { debug!("{:s}", message); } @@ -392,7 +429,6 @@ impl<'a> PrivateWindowHelpers for JSRef<'a, Window> { handle } } - impl Window { pub fn new(cx: *mut JSContext, page: Rc, diff --git a/src/test/content/test_load_event.html b/src/test/content/test_load_event.html index fd8502fcd7d..2ae84b16233 100644 --- a/src/test/content/test_load_event.html +++ b/src/test/content/test_load_event.html @@ -4,13 +4,25 @@