#!/usr/bin/perl -T
no
warnings
qw 'utf8
parenthesis regexp once
qw bareword
syntax';
my
$doc
= new HTML::DOM;
{
our
@ISA
= HTML::DOM::EventTarget::;
sub
event_parent {
${+
shift
}
}
}
my
$child
=
bless
\
do
{
my
$x
},
MyEventTarget
=>;
my
$grandchild
=
bless
\
do
{
my
$x
=
$child
},
'MyEventTarget'
;
{
my
$sub1
=
sub
{};
my
$sub2
=
sub
{};
my
$sub3
=
sub
{};
my
$sub4
=
sub
{};
is_deeply[
$child
->get_event_listeners(
'click'
)],[],
'get_event_listeners initially returns nothing'
;
is_deeply[
$child
->addEventListener(
click
=>
$sub1
)],[],
'addEventListener returns nothing'
;
$child
->addEventListener(
click
=>
$sub2
);
is_deeply[
sort
$child
->get_event_listeners(
'click'
)],
[
sort
$sub1
,
$sub2
],
'get_event_listeners after adding 2'
;
$child
->addEventListener(
click
=>
$sub3
, 1);
$child
->addEventListener(
click
=>
$sub4
, 1);
is_deeply[
sort
$child
->get_event_listeners(
'click'
, 1)],
[
sort
$sub3
,
$sub4
],
'get_event_listeners (for capture phase) after adding 2'
;
is_deeply[
$child
->removeEventListener(
click
=>
$sub1
)],[],
'removeEventListener does nothing'
;
is_deeply[
$child
->get_event_listeners(
'click'
)],
[
$sub2
],
'get_event_listeners after removing one'
;
$child
->removeEventListener(
click
=>
$sub3
, 1);
is_deeply[
$child
->get_event_listeners(
'click'
, 1)],
[
$sub4
],
'get_event_listeners for capture phase after removing one'
;
$child
->addEventListener(
focus
=>
$sub3
);
$child
->addEventListener(
focus
=>
$sub4
);
$child
->addEventListener(
focus
=>
$sub2
, 1);
is_deeply[[
$child
->get_event_listeners(
'click'
)],
[
$child
->get_event_listeners(
'click'
, 1)],
[
sort
(
$child
->get_event_listeners(
'focus'
))],
[
$child
->get_event_listeners(
'focus'
, 1),]],
[[
$sub2
],
[
$sub4
],
[
sort
$sub3
,
$sub4
],
[
$sub2
]],
'different slots for different event types and phases'
;
$child
->onsubmit(
$sub1
);
is +()=
$child
->get_event_listeners(
'submit'
), 1,
'get_event_listeners with attribute event handlers'
;
is +()=
$child
->get_event_listeners(
'submit'
,1), 0,
'event handlers to not apply to the capture phase'
;
}
clear_event_listeners(
$child
,
'click'
,
'focus'
,
'submit'
);
sub
clear_event_listeners {
my
$target
=
shift
;
for
my
$type
(
@_
) {
$target
->removeEventListener(
$type
,
$_
)
for
$target
->get_event_listeners(
$type
);
$target
->removeEventListener(
$type
,
$_
, 1)
for
$target
->get_event_listeners(
$type
, 1);
}
}
my
$event
=
$doc
->createEvent;
my
$event2
=
$doc
->createEvent;
init
$event
type
=>
click
=>
cancellable
=>
1
=>
propagates_up
=> 1;
init
$event2
type
=>
focus
=>
cancellable
=>
0
=>
propagates_up
=> 0;
our
$e
;
$child
->addEventListener(
click
=>
sub
{
$e
.=
'-cclick1'
});
$child
->addEventListener(
click
=>
sub
{
$e
.=
'-cclick2'
});
$child
->addEventListener(
click
=>
sub
{
$e
.=
'-cclick1-capture'
}, 1);
$child
->addEventListener(
click
=>
sub
{
$e
.=
'-cclick2-capture'
}, 1);
$grandchild
->addEventListener(
click
=>
sub
{
$e
.=
'-gcclick1'
});
$grandchild
->addEventListener(
click
=>
sub
{
$e
.=
'-gcclick2'
});
$grandchild
->addEventListener(
click
=>
sub
{
$e
.=
'-gcclick1-capture'
}, 1);
$grandchild
->addEventListener(
click
=>
sub
{
$e
.=
'-gcclick2-capture'
}, 1);
$child
->addEventListener(
focus
=>
sub
{
$e
.=
'-cfocus1'
});
$child
->addEventListener(
focus
=>
sub
{
$e
.=
'-cfocus2'
});
$child
->addEventListener(
focus
=>
sub
{
$e
.=
'-cfocus1-capture'
}, 1);
$child
->addEventListener(
focus
=>
sub
{
$e
.=
'-cfocus2-capture'
}, 1);
$grandchild
->addEventListener(
focus
=>
sub
{
$e
.=
'-gcfocus1'
});
$grandchild
->addEventListener(
focus
=>
sub
{
$e
.=
'-gcfocus2'
});
$grandchild
->addEventListener(
focus
=>
sub
{
$e
.=
'-gcfocus1-capture'
}, 1);
$grandchild
->addEventListener(
focus
=>
sub
{
$e
.=
'-gcfocus2-capture'
}, 1);
$@ =
'drit'
;
$e
=
''
;
ok
$grandchild
->dispatchEvent(
$event
),
'dispatchEvent returns true'
;
like
$e
,
qr/^-cclick(\d)-capture # Each pair can be run in any order,
-cclick(?!\1)\d-capture # hence the (\d) and (?!\1)\d.
-gcclick(\d)
-gcclick(?!\2)\d
-cclick(\d)
-cclick(?!\3)\d
\z/
x,
'order of fizzy event dispatch'
;
$e
=
''
;
$grandchild
->dispatchEvent(
$event2
);
like
$e
,
qr/^-cfocus(\d)-capture # Each pair can be run in any order,
-cfocus(?!\1)\d-capture # hence the (\d) and (?!\1)\d.
-gcfocus(\d)
-gcfocus(?!\2)\d
\z/
x,
'order of flat event dispatch'
;
is $@,
'drit'
,
'event dispatch leaves $@ alone'
;
clear_event_listeners(
$child
,
'click'
,
'focus'
);
clear_event_listeners(
$grandchild
,
'click'
,
'focus'
);
(
$event
=
$doc
->createEvent)->initEvent(
click
=> 1, 1);
$child
->addEventListener(
click
=>
sub
{
$e
.=
$_
[0]->eventPhase }, 1);
$child
->addEventListener(
click
=>
sub
{
$e
.=
$_
[0]->eventPhase }, 1);
$child
->addEventListener(
click
=>
sub
{
$e
.=
$_
[0]->eventPhase });
$child
->addEventListener(
click
=>
sub
{
$e
.=
$_
[0]->eventPhase });
$grandchild
->addEventListener(
click
=>
sub
{
$e
.=
$_
[0]->eventPhase });
$grandchild
->addEventListener(
click
=>
sub
{
$e
.=
$_
[0]->eventPhase });
$e
=
''
;
$grandchild
->dispatchEvent(
$event
);
is
$e
,
'112233'
,
'value of eventPhase during event dispatch'
;
clear_event_listeners(
$child
,
'click'
);
clear_event_listeners(
$grandchild
,
'click'
);
{
$child
->addEventListener(
click
=>
my
$capture1
=
sub
{
$_
[0]->stopPropagation;
$e
.=
'-'
}, 1);
$child
->addEventListener(
click
=>
my
$capture2
=
sub
{
$_
[0]->stopPropagation;
$e
.=
'-'
}, 1);
$grandchild
->addEventListener(
click
=>
my
$at_target1
=
sub
{
$_
[0]->stopPropagation;
$e
.=
'='
});
$grandchild
->addEventListener(
click
=>
my
$at_target2
=
sub
{
$_
[0]->stopPropagation;
$e
.=
'='
});
$child
->addEventListener(
click
=>
my
$fzz1
=
sub
{
$_
[0]->stopPropagation;
$e
.=
'≡'
});
$child
->addEventListener(
click
=>
my
$fzz2
=
sub
{
$_
[0]->stopPropagation;
$e
.=
'≡'
});
$doc
->addEventListener(
click
=>
sub
{
$e
=
"You didn't expect this, did you?"
});
$e
=
''
;
(
$event
=
$doc
->createEvent)->initEvent(
click
=> 1, 1);
$grandchild
->dispatchEvent(
$event
);
is
$e
,
'--'
,
'stopPropagation at capture phase'
;
$child
->removeEventListener(
click
=>
$_
, 1)
for
$capture1
,
$capture2
;
$e
=
''
;
(
$event
=
$doc
->createEvent)->initEvent(
click
=> 1, 1);
$grandchild
->dispatchEvent(
$event
);
is
$e
,
'=='
,
'stopPropagation at the target'
;
$grandchild
->removeEventListener(
click
=>
$_
)
for
$at_target1
,
$at_target2
;
$e
=
''
;
(
$event
=
$doc
->createEvent)->initEvent(
click
=> 1, 1);
$grandchild
->dispatchEvent(
$event
);
is
$e
,
'≡≡'
,
'stopPropagation at the bubbly phase'
;
}
clear_event_listeners(
$child
,
'click'
);
clear_event_listeners(
$grandchild
,
'click'
);
clear_event_listeners(
$doc
,
'click'
);
$child
->addEventListener(
cLick
=>
sub
{
is
$_
[0]->currentTarget,
$child
,
'currentTarget at capture stage'
;
is
$_
[0]->target,
$grandchild
,
'"target" attr during capture phase'
;
}, 1);
$grandchild
->addEventListener(
clIck
=>
sub
{
is
$_
[0]->currentTarget,
$grandchild
,
'currentTarget at the target'
;
is
$_
[0]->target,
$grandchild
,
'"target" attr at the target'
;
});
$child
->addEventListener(
cliCk
=>
sub
{
is
scalar
$_
[0]->preventDefault,
undef
,
'return val of preventDefault'
;
is
$_
[0]->currentTarget,
$child
,
'currentTarget while bubbles are being blown'
;
is
$_
[0]->target,
$grandchild
,
'"target" attr while froth is rising'
;
});
(
$event
=
$doc
->createEvent)->initEvent(
click
=> 1, 1);
ok!
$grandchild
->dispatchEvent(
$event
),
'preventDefault makes dispatchEvent return false'
;
clear_event_listeners(
$child
,
'click'
);
clear_event_listeners(
$grandchild
,
'click'
);
$grandchild
->addEventListener(
click
=>
sub
{
$e
=
'did it'
;
$_
[0]->preventDefault
});
(
$event
=
$doc
->createEvent)->initEvent(
click
=> 1, 0);
ok
$grandchild
->dispatchEvent(
$event
),
'preventDefault has no effect on uncancelable actions'
;
is
$e
,
'did it'
,
'And, yes, preventDefault *was* actually called.'
;
clear_event_listeners(
$grandchild
,
'click'
);
{
(
$event
=
$doc
->createEvent)->initEvent(
'click'
,1,1);
$grandchild
->onclick(
sub
{ 0 });
ok !
$grandchild
->dispatchEvent(
$event
),
'defined false retval from attr event handler calls preventDefault'
;
(
$event
=
$doc
->createEvent)->initEvent(
'click'
,1,1);
$grandchild
->onclick(
sub
{});
ok
$grandchild
->dispatchEvent(
$event
),
'undef retval from attr event handler does not call preventDefault'
;
(
$event
=
$doc
->createEvent)->initEvent(
'mouseover'
,1,1);
$grandchild
->onmouseover(
sub
{});
ok
$grandchild
->dispatchEvent(
$event
),
'undef retval from onmouseover handler does not call preventDefault'
;
(
$event
=
$doc
->createEvent)->initEvent(
'mouseover'
,1,1);
$grandchild
->onmouseover(
sub
{1});
ok !
$grandchild
->dispatchEvent(
$event
),
'true retval from onmouseover handler calls preventDefault'
;
my
@scratch
;
*Function::call_with
=
sub
{
@scratch
=
@_
};
$grandchild
->onclick(
bless
[],
'Function'
);
$grandchild
->trigger_event(
'click'
);
is
$scratch
[1],
$grandchild
,
'target is passed to call_with'
;
isa_ok
$scratch
[2],
'HTML::DOM::Event'
,
'second arg to call_with'
;
our
@ISA
= HTML::DOM::EventTarget::;
sub
get_event_listeners { ${+
shift
}->get_event_listeners(
@_
) }
sub
event_handler { ${+
shift
}->event_handler(
@_
) }
sub
addEventListener { ${+
shift
}->addEventListener(
@_
) }
sub
removeEventListener { ${+
shift
}->removeEventListener(
@_
) }
}
my
$delegate
=
bless
\
do
{
my
$x
=
$grandchild
}, DelegatingEventTarget::;
$delegate
->trigger_event(
'click'
);
(
$event
=
$doc
->createEvent)->init(
type
=>
'click'
);
is
$scratch
[1],
$delegate
,
'event handler wrappers can be transferred to other objects'
;
}
$event
=
$doc
->createEvent;
eval
{
$child
->dispatchEvent(
$event
);
};
isa_ok $@,
'HTML::DOM::Exception'
,
'$@ (after dispatchEvent with an uninited event)'
;
cmp_ok $@,
'=='
, HTML::DOM::Exception::UNSPECIFIED_EVENT_TYPE_ERR,
'dispatchEvent with an uninited event throws the '
.
'appropriate error'
;
$event
->initEvent(
undef
, 1, 1);
eval
{
$child
->dispatchEvent(
$event
);
};
isa_ok $@,
'HTML::DOM::Exception'
,
'$@ (after dispatchEvent with no event type)'
;
cmp_ok $@,
'=='
, HTML::DOM::Exception::UNSPECIFIED_EVENT_TYPE_ERR,
'dispatchEvent with an no event type throws the '
.
'appropriate error'
;
$event
->initEvent(
''
=> 1, 1);
eval
{
$child
->dispatchEvent(
$event
);
};
isa_ok $@,
'HTML::DOM::Exception'
,
'$@ (after dispatchEvent with "" for the event type)'
;
cmp_ok $@,
'=='
, HTML::DOM::Exception::UNSPECIFIED_EVENT_TYPE_ERR,
'dispatchEvent with "" for the event type throws the '
.
'appropriate error'
;
clear_event_listeners(
$grandchild
,
'click'
);
$grandchild
->addEventListener(
clink
=>
sub
{
$_
[0]->preventDefault
});
my
@def
= (
default
=>
sub
{
$e
=
$_
[0];
});
$e
=
''
;
(
$event
=
$doc
->createEvent)->initEvent(
clink
=> 1, 1);
$grandchild
->trigger_event(
$event
,
@def
);
is
$e
,
''
,
'event objects passed to trigger_event can be stopped'
;
$grandchild
->trigger_event(
'clink'
,
@def
);
is
$e
,
''
,
'event names passed to trigger_event can be stopped'
;
$e
=
''
;
(
$event
=
$doc
->createEvent)->initEvent(
clink
=> 1, 0);
$grandchild
->trigger_event(
$event
,
@def
);
is
$e
,
$event
,
'the default event was run when an obj was passed to trigger_event'
;
clear_event_listeners(
$grandchild
,
'clink'
);
$e
=
''
;
$grandchild
->trigger_event(
'clink'
,
@def
);
is
$e
->type,
'clink'
,
'$event->type when an event name is passed to trigger_event'
;
is
$e
->target,
$grandchild
,
'$event->target when an event name is passed to trigger_event'
;
undef
$e
;
{
no
warnings
'once'
;
my
$e
;
local
*MyEventTarget
'error_handler =
sub
{
sub
{
$e
= $@ }};
$grandchild
->addEventListener(
foo
=>
sub
{
die
"67\n"
});
$grandchild
->trigger_event(
'foo'
);
is
$e
,
"67\n"
,
'error_handler gets called'
;
}
{
no
warnings
qw 'redefine
once';
my
$e
;
local
*MyEventTarget
'event_listeners_enabled =
sub
{ 1 };
$grandchild
->addEventListener(
foo
=>
sub
{ ++
$e
});
$grandchild
->trigger_event(
'foo'
);
is
$e
, 1,
'event handlers run when event_listeners_enabled returns true'
;
local
*MyEventTarget
'event_listeners_enabled =
sub
{ 0 };
$grandchild
->trigger_event(
'foo'
);
is
$e
, 1,
'event handlers don\'t run if event_listeners_enabled is false'
;
local
*MyEventTarget
'event_listeners_enabled =
sub
{ 1 };
local
*MyDoc
'event_listeners_enabled =
sub
{ 0 };
local
*MyEventTarget
'ownerDocument = sub { bless[], '
MyDoc' };
$grandchild
->trigger_event(
'foo'
);
is
$e
, 2,
'An event_listeners_enabled method on the event'
;
local
*MyEventTarget
'event_listeners_enabled =
sub
{ 0 };
local
*MyDoc
'event_listeners_enabled =
sub
{ 1 };
$grandchild
->trigger_event(
'foo'
);
is
$e
, 2,
' target prevents ownerDocument from being checked.'
;
undef
*MyEventTarget
'event_listeners_enabled;
$grandchild
->trigger_event(
'foo'
);
is
$e
, 3,
'fallback to event_listeners_enabled'
;
local
*MyDoc
'event_listeners_enabled =
sub
{ 0 };
$grandchild
->trigger_event(
'foo'
);
is
$e
, 3,
' on the ownerDocument'
;
$grandchild
->trigger_event(
'foo'
,
default
=>
sub
{
is
$_
[0]->target,
$grandchild
,
'the event’s target is set when event handlers are disabled'
});
}
{
my
$scratch
;
my
$sub1
=
sub
{
$scratch
.=
"one called "
};
my
$sub2
=
sub
{
$scratch
.=
"two called "
};
is +()=
$child
->onbdext, 0,
'null retval from on*'
;
is +()=
$child
->onbdext(
$sub1
),0,
'null retval from on* initial assignment'
;
is
$child
->onbdext(
$sub2
),
$sub1
,
'on* returns old value'
;
is
$child
->onbdext,
$sub2
,
'on* with no args after assignment'
;
$child
->trigger_event(
'bdext'
);
is
$scratch
,
"two called "
,
'on* registers event listener and removes old one'
;
is
$child
->attr_event_listener(
'bdext'
),
$sub2
,
'attr_event_listener returns the same thing as on*'
;
is
$child
->attr_event_listener(
'bdext'
,
$sub1
),
$sub2
,
'setting attr_event_listener returns old val'
;
is
$child
->onbdext,
$sub1
,
'and the change applies to on*'
;
}
{
eval
{
'dwext'
->HTML::DOM::Node::dwed; };
like $@,
qr/^Can't locate object method "dwed" via package "HTML::DOM::Node"/
;
}