Add Event and EventTarget hierarchy via gross AbstractFoo mechanism.

This commit is contained in:
Josh Matthews 2013-10-01 17:01:46 -04:00
parent 7ecf5abbbd
commit bb97fd13f3
9 changed files with 327 additions and 86 deletions

View file

@ -182,6 +182,9 @@ DOMInterfaces = {
},
'Event': {
'nativeType': 'AbstractEvent',
'concreteType': 'Event',
'pointerType': '',
},
'EventListener': {
@ -189,6 +192,10 @@ DOMInterfaces = {
},
'EventTarget': {
'nativeType': 'AbstractEventTarget',
'concreteType': 'EventTarget',
'pointerType': '',
'needsAbstract': ['dispatchEvent']
},
'FileList': [
@ -277,6 +284,9 @@ DOMInterfaces = {
}],
'MouseEvent': {
'nativeType': 'AbstractEvent',
'concreteType': 'MouseEvent',
'pointerType': '',
},
'Navigator': {
@ -374,6 +384,9 @@ DOMInterfaces = {
}],
'UIEvent': {
'nativeType': 'AbstractEvent',
'concreteType': 'UIEvent',
'pointerType': '',
},
'ValidityState': {

View file

@ -2410,12 +2410,13 @@ class Argument():
"""
A class for outputting the type and name of an argument
"""
def __init__(self, argType, name, default=None):
def __init__(self, argType, name, default=None, mutable=False):
self.argType = argType
self.name = name
self.default = default
self.mutable = mutable
def declare(self):
string = self.name + ((': ' + self.argType) if self.argType else '')
string = ('mut ' if self.mutable else '') + self.name + ((': ' + self.argType) if self.argType else '')
#XXXjdm Support default arguments somehow :/
#if self.default is not None:
# string += " = " + self.default
@ -5225,6 +5226,8 @@ class CGBindingRoot(CGThing):
'dom::bindings::proxyhandler::*',
'dom::document::AbstractDocument',
'dom::node::{AbstractNode, ScriptView}',
'dom::eventtarget::AbstractEventTarget',
'dom::event::AbstractEvent',
'servo_util::vec::zip_copies',
'std::cast',
'std::libc',
@ -5652,6 +5655,11 @@ class CGCallback(CGClass):
# CallSetup should re-throw exceptions on aRv.
args.append(Argument("ExceptionHandling", "aExceptionHandling",
"eReportExceptions"))
# Ensure the first argument is mutable
args[0] = Argument(args[0].argType, args[0].name, args[0].default, mutable=True)
method.args[2] = args[0]
# And now insert our template argument.
argsWithoutThis = list(args)
args.insert(0, Argument("@mut T", "thisObj"))
@ -5661,7 +5669,7 @@ class CGCallback(CGClass):
args.insert(0, Argument(None, "&self"))
argsWithoutThis.insert(0, Argument(None, "&self"))
setupCall = ("let s = CallSetup::new(cx_for_dom_object(${cxProvider}), aExceptionHandling);\n"
setupCall = ("let s = CallSetup::new(cx_for_dom_object(&mut ${cxProvider}), aExceptionHandling);\n"
"if s.GetContext().is_null() {\n"
" return${errorReturn};\n"
"}\n")
@ -5676,7 +5684,7 @@ class CGCallback(CGClass):
"errorReturn" : method.getDefaultRetval(),
"callArgs" : ", ".join(argnamesWithThis),
"methodName": 'self.' + method.name,
"cxProvider": 'thisObj'
"cxProvider": '*thisObj'
})
bodyWithoutThis = string.Template(
setupCall +

View file

@ -768,6 +768,7 @@ pub enum Error {
NotFound,
HierarchyRequest,
InvalidCharacter,
NotSupported
}
pub type Fallible<T> = Result<T, Error>;
@ -841,7 +842,7 @@ pub fn CreateDOMGlobal(cx: *JSContext, class: *JSClass) -> *JSObject {
}
#[fixed_stack_segment]
fn cx_for_dom_wrapper(obj: *JSObject) -> *JSContext {
fn cx_for_dom_reflector(obj: *JSObject) -> *JSContext {
unsafe {
let global = GetGlobalForObjectCrossCompartment(obj);
let clasp = JS_GetClass(global);
@ -860,8 +861,8 @@ fn cx_for_dom_wrapper(obj: *JSObject) -> *JSContext {
}
}
pub fn cx_for_dom_object<T: Reflectable>(obj: @mut T) -> *JSContext {
cx_for_dom_wrapper(obj.reflector().get_jsobject())
pub fn cx_for_dom_object<T: Reflectable>(obj: &mut T) -> *JSContext {
cx_for_dom_reflector(obj.reflector().get_jsobject())
}
/// Check if an element name is valid. See http://www.w3.org/TR/xml/#NT-Name

View file

@ -5,16 +5,20 @@
use dom::comment::Comment;
use dom::bindings::codegen::DocumentBinding;
use dom::bindings::utils::{DOMString, ErrorResult, Fallible};
use dom::bindings::utils::{Reflectable, Reflector, DerivedWrapper};
use dom::bindings::utils::{is_valid_element_name, InvalidCharacter, Traceable, null_str_as_empty, null_str_as_word_null};
use dom::bindings::utils::{Reflectable, Reflector, DerivedWrapper, NotSupported};
use dom::bindings::utils::{is_valid_element_name, InvalidCharacter, Traceable};
use dom::bindings::utils::{null_str_as_empty_ref, null_str_as_empty, null_str_as_word_null};
use dom::documentfragment::DocumentFragment;
use dom::element::{Element};
use dom::element::{HTMLHeadElementTypeId, HTMLTitleElementTypeId};
use dom::event::Event;
use dom::event::{AbstractEvent, Event, HTMLEventTypeId, UIEventTypeId};
use dom::htmlcollection::HTMLCollection;
use dom::htmldocument::HTMLDocument;
use dom::htmlelement::HTMLElement;
use dom::mouseevent::MouseEvent;
use dom::node::{AbstractNode, ScriptView, Node, ElementNodeTypeId, DocumentNodeTypeId};
use dom::text::Text;
use dom::uievent::UIEvent;
use dom::window::Window;
use dom::htmltitleelement::HTMLTitleElement;
use html::hubbub_html_parser::build_element_from_tag;
@ -256,9 +260,13 @@ impl Document {
Comment::new(null_str_as_word_null(data), abstract_self)
}
pub fn CreateEvent(&self, interface: &DOMString) -> Fallible<@mut Event> {
//FIXME: We need to do a proper Event inheritance simulation
Ok(Event::new(self.window, interface))
pub fn CreateEvent(&self, interface: &DOMString) -> Fallible<AbstractEvent> {
match null_str_as_empty_ref(interface) {
"UIEvents" => Ok(UIEvent::new(self.window, UIEventTypeId)),
"MouseEvents" => Ok(MouseEvent::new(self.window)),
"HTMLEvents" => Ok(Event::new(self.window, HTMLEventTypeId)),
_ => Err(NotSupported)
}
}
pub fn Title(&self, _: AbstractDocument) -> DOMString {

View file

@ -2,17 +2,23 @@
* 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::eventtarget::EventTarget;
use dom::eventtarget::AbstractEventTarget;
use dom::window::Window;
use dom::bindings::codegen::EventBinding;
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object, DerivedWrapper};
use dom::bindings::utils::{DOMString, ErrorResult, Fallible, null_str_as_word_null};
use dom::mouseevent::MouseEvent;
use dom::uievent::UIEvent;
use geom::point::Point2D;
use js::jsapi::{JSObject, JSContext};
use js::jsapi::{JSObject, JSContext, JSVal};
use js::glue::RUST_OBJECT_TO_JSVAL;
use script_task::page_from_context;
use std::cast;
use std::unstable::raw::Box;
pub enum Event_ {
ResizeEvent(uint, uint),
ReflowEvent,
@ -21,7 +27,116 @@ pub enum Event_ {
MouseUpEvent(uint, Point2D<f32>),
}
pub struct AbstractEvent {
event: *mut Box<Event>
}
impl AbstractEvent {
pub fn from_box(box: *mut Box<Event>) -> AbstractEvent {
AbstractEvent {
event: box
}
}
//
// Downcasting borrows
//
fn transmute<'a, T>(&'a self) -> &'a T {
unsafe {
let box: *Box<T> = self.event as *Box<T>;
&(*box).data
}
}
fn transmute_mut<'a, T>(&'a self) -> &'a mut T {
unsafe {
let box: *mut Box<T> = self.event as *mut Box<T>;
&mut (*box).data
}
}
pub fn type_id(&self) -> EventTypeId {
self.event().type_id
}
pub fn event<'a>(&'a self) -> &'a Event {
self.transmute()
}
pub fn mut_event<'a>(&'a self) -> &'a mut Event {
self.transmute_mut()
}
pub fn is_uievent(&self) -> bool {
self.type_id() == UIEventTypeId
}
pub fn uievent<'a>(&'a self) -> &'a UIEvent {
assert!(self.is_uievent());
self.transmute()
}
pub fn mut_uievent<'a>(&'a self) -> &'a mut UIEvent {
assert!(self.is_uievent());
self.transmute_mut()
}
pub fn is_mouseevent(&self) -> bool {
self.type_id() == MouseEventTypeId
}
pub fn mouseevent<'a>(&'a self) -> &'a MouseEvent {
assert!(self.is_mouseevent());
self.transmute()
}
pub fn mut_mouseevent<'a>(&'a self) -> &'a mut MouseEvent {
assert!(self.is_mouseevent());
self.transmute_mut()
}
}
impl DerivedWrapper for AbstractEvent {
#[fixed_stack_segment]
fn wrap(&mut self, _cx: *JSContext, _scope: *JSObject, vp: *mut JSVal) -> i32 {
let wrapper = self.reflector().get_jsobject();
if wrapper.is_not_null() {
unsafe { *vp = RUST_OBJECT_TO_JSVAL(wrapper) };
return 1;
}
unreachable!()
}
}
impl Reflectable for AbstractEvent {
fn reflector<'a>(&'a self) -> &'a Reflector {
self.event().reflector()
}
fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector {
self.mut_event().mut_reflector()
}
fn wrap_object_shared(@mut self, _cx: *JSContext, _scope: *JSObject) -> *JSObject {
fail!(~"doesn't make any sense");
}
fn GetParentObject(&self, cx: *JSContext) -> Option<@mut Reflectable> {
self.event().GetParentObject(cx)
}
}
#[deriving(Eq)]
pub enum EventTypeId {
HTMLEventTypeId,
UIEventTypeId,
MouseEventTypeId,
KeyEventTypeId
}
pub struct Event {
type_id: EventTypeId,
reflector_: Reflector,
type_: ~str,
default_prevented: bool,
@ -31,10 +146,11 @@ pub struct Event {
}
impl Event {
pub fn new_inherited(type_: &DOMString) -> Event {
pub fn new_inherited(type_id: EventTypeId) -> Event {
Event {
type_id: type_id,
reflector_: Reflector::new(),
type_: null_str_as_word_null(type_),
type_: ~"",
default_prevented: false,
cancelable: true,
bubbles: true,
@ -42,8 +158,18 @@ impl Event {
}
}
pub fn new(window: @mut Window, type_: &DOMString) -> @mut Event {
reflect_dom_object(@mut Event::new_inherited(type_), window, EventBinding::Wrap)
//FIXME: E should be bounded by some trait that is only implemented for Event types
pub fn as_abstract<E>(event: @mut E) -> AbstractEvent {
// This surrenders memory management of the event!
AbstractEvent {
event: unsafe { cast::transmute(event) },
}
}
pub fn new(window: @mut Window, type_id: EventTypeId) -> AbstractEvent {
let ev = reflect_dom_object(@mut Event::new_inherited(type_id), window,
EventBinding::Wrap);
Event::as_abstract(ev)
}
pub fn EventPhase(&self) -> u16 {
@ -54,11 +180,11 @@ impl Event {
Some(self.type_.clone())
}
pub fn GetTarget(&self) -> Option<@mut EventTarget> {
pub fn GetTarget(&self) -> Option<AbstractEventTarget> {
None
}
pub fn GetCurrentTarget(&self) -> Option<@mut EventTarget> {
pub fn GetCurrentTarget(&self) -> Option<AbstractEventTarget> {
None
}
@ -92,7 +218,7 @@ impl Event {
type_: &DOMString,
bubbles: bool,
cancelable: bool) -> ErrorResult {
self.type_ = type_.to_str();
self.type_ = null_str_as_word_null(type_);
self.cancelable = cancelable;
self.bubbles = bubbles;
Ok(())
@ -104,8 +230,10 @@ impl Event {
pub fn Constructor(global: @mut Window,
type_: &DOMString,
_init: &EventBinding::EventInit) -> Fallible<@mut Event> {
Ok(Event::new(global, type_))
init: &EventBinding::EventInit) -> Fallible<AbstractEvent> {
let ev = Event::new(global, HTMLEventTypeId);
ev.mut_event().InitEvent(type_, init.bubbles, init.cancelable);
Ok(ev)
}
}

View file

@ -4,14 +4,19 @@
use dom::bindings::callback::eReportExceptions;
use dom::bindings::codegen::EventTargetBinding;
use dom::bindings::utils::{Reflectable, Reflector, DOMString, Fallible};
use dom::bindings::utils::{Reflectable, Reflector, DOMString, Fallible, DerivedWrapper};
use dom::bindings::utils::null_str_as_word_null;
use dom::bindings::codegen::EventListenerBinding::EventListener;
use dom::event::Event;
use dom::event::AbstractEvent;
use dom::node::{AbstractNode, ScriptView};
use script_task::page_from_context;
use js::jsapi::{JSObject, JSContext};
use js::jsapi::{JSObject, JSContext, JSVal};
use js::glue::RUST_OBJECT_TO_JSVAL;
use std::cast;
use std::hashmap::HashMap;
use std::unstable::raw::Box;
pub struct EventTarget {
reflector_: Reflector,
@ -19,6 +24,80 @@ pub struct EventTarget {
bubbling_handlers: HashMap<~str, ~[EventListener]>
}
pub struct AbstractEventTarget {
eventtarget: *mut Box<EventTarget>
}
impl AbstractEventTarget {
pub fn from_box(box: *mut Box<EventTarget>) -> AbstractEventTarget {
AbstractEventTarget {
eventtarget: box
}
}
pub fn from_node(node: AbstractNode<ScriptView>) -> AbstractEventTarget {
unsafe {
cast::transmute(node)
}
}
//
// Downcasting borrows
//
fn transmute<'a, T>(&'a self) -> &'a T {
unsafe {
let box: *Box<T> = self.eventtarget as *Box<T>;
&(*box).data
}
}
fn transmute_mut<'a, T>(&'a mut self) -> &'a mut T {
unsafe {
let box: *mut Box<T> = self.eventtarget as *mut Box<T>;
&mut (*box).data
}
}
fn eventtarget<'a>(&'a self) -> &'a EventTarget {
self.transmute()
}
fn mut_eventtarget<'a>(&'a mut self) -> &'a mut EventTarget {
self.transmute_mut()
}
}
impl DerivedWrapper for AbstractEventTarget {
#[fixed_stack_segment]
fn wrap(&mut self, _cx: *JSContext, _scope: *JSObject, vp: *mut JSVal) -> i32 {
let wrapper = self.reflector().get_jsobject();
if wrapper.is_not_null() {
unsafe { *vp = RUST_OBJECT_TO_JSVAL(wrapper) };
return 1;
}
unreachable!()
}
}
impl Reflectable for AbstractEventTarget {
fn reflector<'a>(&'a self) -> &'a Reflector {
self.eventtarget().reflector()
}
fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector {
self.mut_eventtarget().mut_reflector()
}
fn wrap_object_shared(@mut self, _cx: *JSContext, _scope: *JSObject) -> *JSObject {
unreachable!()
}
fn GetParentObject(&self, cx: *JSContext) -> Option<@mut Reflectable> {
self.eventtarget().GetParentObject(cx)
}
}
impl EventTarget {
pub fn new() -> EventTarget {
EventTarget {
@ -46,7 +125,7 @@ impl EventTarget {
} else {
&mut self.bubbling_handlers
};
let entry = handlers.find_or_insert_with(ty.to_str(), |_| ~[]);
let entry = handlers.find_or_insert_with(null_str_as_word_null(ty), |_| ~[]);
if entry.position_elem(listener).is_none() {
entry.push((*listener).clone());
}
@ -63,7 +142,7 @@ impl EventTarget {
} else {
&mut self.bubbling_handlers
};
let mut entry = handlers.find_mut(&ty.to_str());
let mut entry = handlers.find_mut(&null_str_as_word_null(ty));
for entry in entry.mut_iter() {
let position = entry.position_elem(listener);
for &position in position.iter() {
@ -73,24 +152,25 @@ impl EventTarget {
}
}
pub fn DispatchEvent(&self, event: @mut Event) -> Fallible<bool> {
pub fn DispatchEvent(&self, _abstract_self: AbstractEventTarget, event: AbstractEvent) -> Fallible<bool> {
//FIXME: get proper |this| object
let maybe_handlers = self.capturing_handlers.find(&event.type_);
let type_ = event.event().type_.clone();
let maybe_handlers = self.capturing_handlers.find(&type_);
for handlers in maybe_handlers.iter() {
for handler in handlers.iter() {
handler.HandleEvent__(event, eReportExceptions);
}
}
if event.bubbles {
let maybe_handlers = self.bubbling_handlers.find(&event.type_);
if event.event().bubbles {
let maybe_handlers = self.bubbling_handlers.find(&type_);
for handlers in maybe_handlers.iter() {
for handler in handlers.iter() {
handler.HandleEvent__(event, eReportExceptions);
}
}
}
Ok(!event.DefaultPrevented())
Ok(!event.event().DefaultPrevented())
}
}

View file

@ -5,7 +5,8 @@
use dom::bindings::codegen::MouseEventBinding;
use dom::bindings::utils::{ErrorResult, Fallible, DOMString};
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::eventtarget::EventTarget;
use dom::event::{AbstractEvent, Event, MouseEventTypeId};
use dom::eventtarget::AbstractEventTarget;
use dom::uievent::UIEvent;
use dom::window::Window;
use dom::windowproxy::WindowProxy;
@ -23,38 +24,42 @@ pub struct MouseEvent {
alt_key: bool,
meta_key: bool,
button: u16,
related_target: Option<@mut EventTarget>
related_target: Option<AbstractEventTarget>
}
impl MouseEvent {
pub fn new(window: @mut Window, type_: &DOMString, can_bubble: bool, cancelable: bool,
view: Option<@mut WindowProxy>, detail: i32, screen_x: i32,
screen_y: i32, client_x: i32, client_y: i32, ctrl_key: bool,
shift_key: bool, alt_key: bool, meta_key: bool, button: u16,
_buttons: u16, related_target: Option<@mut EventTarget>) -> @mut MouseEvent {
let ev = @mut MouseEvent {
parent: UIEvent::new_inherited(type_, can_bubble, cancelable, view, detail),
screen_x: screen_x,
screen_y: screen_y,
client_x: client_x,
client_y: client_y,
ctrl_key: ctrl_key,
shift_key: shift_key,
alt_key: alt_key,
meta_key: meta_key,
button: button,
related_target: related_target
};
reflect_dom_object(ev, window, MouseEventBinding::Wrap)
pub fn new_inherited() -> MouseEvent {
MouseEvent {
parent: UIEvent::new_inherited(MouseEventTypeId),
screen_x: 0,
screen_y: 0,
client_x: 0,
client_y: 0,
ctrl_key: false,
shift_key: false,
alt_key: false,
meta_key: false,
button: 0,
related_target: None
}
}
pub fn new(window: @mut Window) -> AbstractEvent {
Event::as_abstract(reflect_dom_object(@mut MouseEvent::new_inherited(),
window,
MouseEventBinding::Wrap))
}
pub fn Constructor(owner: @mut Window,
type_: &DOMString,
init: &MouseEventBinding::MouseEventInit) -> Fallible<@mut MouseEvent> {
Ok(MouseEvent::new(owner, type_, init.bubbles, init.cancelable, init.view, init.detail,
init.screenX, init.screenY, init.clientX, init.clientY,
init.ctrlKey, init.shiftKey, init.altKey, init.metaKey,
init.button, init.buttons, init.relatedTarget))
init: &MouseEventBinding::MouseEventInit) -> Fallible<AbstractEvent> {
let ev = MouseEvent::new(owner);
ev.mut_mouseevent().InitMouseEvent(type_, init.bubbles, init.cancelable, init.view,
init.detail, init.screenX, init.screenY,
init.clientX, init.clientY, init.ctrlKey,
init.altKey, init.shiftKey, init.metaKey,
init.button, init.relatedTarget);
Ok(ev)
}
pub fn ScreenX(&self) -> i32 {
@ -98,7 +103,7 @@ impl MouseEvent {
0
}
pub fn GetRelatedTarget(&self) -> Option<@mut EventTarget> {
pub fn GetRelatedTarget(&self) -> Option<AbstractEventTarget> {
self.related_target
}
@ -122,7 +127,7 @@ impl MouseEvent {
shiftKeyArg: bool,
metaKeyArg: bool,
buttonArg: u16,
relatedTargetArg: Option<@mut EventTarget>) -> ErrorResult {
relatedTargetArg: Option<AbstractEventTarget>) -> ErrorResult {
self.parent.InitUIEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg);
self.screen_x = screenXArg;
self.screen_y = screenYArg;

View file

@ -6,7 +6,7 @@ use dom::bindings::codegen::UIEventBinding;
use dom::bindings::utils::{DOMString, Fallible};
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::node::{AbstractNode, ScriptView};
use dom::event::Event;
use dom::event::{AbstractEvent, Event, EventTypeId, UIEventTypeId};
use dom::window::Window;
use dom::windowproxy::WindowProxy;
@ -14,36 +14,33 @@ use js::jsapi::{JSObject, JSContext};
pub struct UIEvent {
parent: Event,
can_bubble: bool,
cancelable: bool,
view: Option<@mut WindowProxy>,
detail: i32
}
impl UIEvent {
pub fn new_inherited(type_: &DOMString, can_bubble: bool, cancelable: bool,
view: Option<@mut WindowProxy>, detail: i32) -> UIEvent {
pub fn new_inherited(type_id: EventTypeId) -> UIEvent {
UIEvent {
parent: Event::new_inherited(type_),
can_bubble: can_bubble,
cancelable: cancelable,
view: view,
detail: detail
parent: Event::new_inherited(type_id),
view: None,
detail: 0
}
}
pub fn new(window: @mut Window, type_: &DOMString, can_bubble: bool, cancelable: bool,
view: Option<@mut WindowProxy>, detail: i32) -> @mut UIEvent {
reflect_dom_object(@mut UIEvent::new_inherited(type_, can_bubble, cancelable, view, detail),
pub fn new(window: @mut Window, type_id: EventTypeId) -> AbstractEvent {
let ev = reflect_dom_object(@mut UIEvent::new_inherited(type_id),
window,
UIEventBinding::Wrap)
UIEventBinding::Wrap);
Event::as_abstract(ev)
}
pub fn Constructor(owner: @mut Window,
type_: &DOMString,
init: &UIEventBinding::UIEventInit) -> Fallible<@mut UIEvent> {
Ok(UIEvent::new(owner, type_, init.parent.bubbles, init.parent.cancelable,
init.view, init.detail))
init: &UIEventBinding::UIEventInit) -> Fallible<AbstractEvent> {
let ev = UIEvent::new(owner, UIEventTypeId);
ev.mut_uievent().InitUIEvent(type_, init.parent.bubbles, init.parent.cancelable,
init.view, init.detail);
Ok(ev)
}
pub fn GetView(&self) -> Option<@mut WindowProxy> {
@ -61,8 +58,6 @@ impl UIEvent {
view: Option<@mut WindowProxy>,
detail: i32) {
self.parent.InitEvent(type_, can_bubble, cancelable);
self.can_bubble = can_bubble;
self.cancelable = cancelable;
self.view = view;
self.detail = detail;
}

View file

@ -4,14 +4,17 @@
</head>
<body>
<script>
var saw_event = false;
function onFoopy() {
window.removeEventListener('foopy', onFoopy);
finish();
saw_event = true;
}
window.addEventListener('foopy', onFoopy);
var ev = document.createEvent('Event');
var ev = document.createEvent('HTMLEvents');
ev.initEvent('foopy', true, true);
window.dispatchEvent(ev);
is(saw_event, true);
finish();
</script>
</body>
</html>