mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Auto merge of #27413 - utsavoza:ugo/issue-26958/26-07-2020, r=nox
Fire mouseenter and mouseleave events The PR primarily consists of changes for: - Fixing the order in which `mousemove` events are fired. - Firing `mouseenter` and `mouseleave` events. --- - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #26958 - [ ] There are tests for these changes
This commit is contained in:
commit
0021a82cb2
8 changed files with 320 additions and 72 deletions
|
@ -940,7 +940,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
|
||||||
let results = self.hit_test_at_point(cursor);
|
let results = self.hit_test_at_point(cursor);
|
||||||
if let Some(item) = results.items.first() {
|
if let Some(item) = results.items.first() {
|
||||||
let node_address = Some(UntrustedNodeAddress(item.tag.0 as *const c_void));
|
let node_address = Some(UntrustedNodeAddress(item.tag.0 as *const c_void));
|
||||||
let event = MouseMoveEvent(Some(item.point_in_viewport.to_untyped()), node_address, 0);
|
let event = MouseMoveEvent(item.point_in_viewport.to_untyped(), node_address, 0);
|
||||||
let pipeline_id = PipelineId::from_webrender(item.pipeline);
|
let pipeline_id = PipelineId::from_webrender(item.pipeline);
|
||||||
let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
|
let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
|
||||||
if let Err(e) = self.constellation_chan.send(msg) {
|
if let Err(e) = self.constellation_chan.send(msg) {
|
||||||
|
|
|
@ -189,10 +189,13 @@ pub enum TouchEventResult {
|
||||||
Forwarded,
|
Forwarded,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum FireMouseEventType {
|
pub enum FireMouseEventType {
|
||||||
Move,
|
Move,
|
||||||
Over,
|
Over,
|
||||||
Out,
|
Out,
|
||||||
|
Enter,
|
||||||
|
Leave,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FireMouseEventType {
|
impl FireMouseEventType {
|
||||||
|
@ -201,6 +204,8 @@ impl FireMouseEventType {
|
||||||
&FireMouseEventType::Move => "mousemove",
|
&FireMouseEventType::Move => "mousemove",
|
||||||
&FireMouseEventType::Over => "mouseover",
|
&FireMouseEventType::Over => "mouseover",
|
||||||
&FireMouseEventType::Out => "mouseout",
|
&FireMouseEventType::Out => "mouseout",
|
||||||
|
&FireMouseEventType::Enter => "mouseenter",
|
||||||
|
&FireMouseEventType::Leave => "mouseleave",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -555,7 +560,8 @@ impl Document {
|
||||||
|
|
||||||
let new_dirty_root = element
|
let new_dirty_root = element
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.common_ancestor(dirty_root.upcast(), ShadowIncluding::Yes);
|
.common_ancestor(dirty_root.upcast(), ShadowIncluding::Yes)
|
||||||
|
.expect("Couldn't find common ancestor");
|
||||||
|
|
||||||
let mut has_dirty_descendants = true;
|
let mut has_dirty_descendants = true;
|
||||||
for ancestor in dirty_root
|
for ancestor in dirty_root
|
||||||
|
@ -1343,6 +1349,8 @@ impl Document {
|
||||||
client_point: Point2D<f32>,
|
client_point: Point2D<f32>,
|
||||||
target: &EventTarget,
|
target: &EventTarget,
|
||||||
event_name: FireMouseEventType,
|
event_name: FireMouseEventType,
|
||||||
|
can_bubble: EventBubbles,
|
||||||
|
cancelable: EventCancelable,
|
||||||
pressed_mouse_buttons: u16,
|
pressed_mouse_buttons: u16,
|
||||||
) {
|
) {
|
||||||
let client_x = client_point.x.to_i32().unwrap_or(0);
|
let client_x = client_point.x.to_i32().unwrap_or(0);
|
||||||
|
@ -1351,8 +1359,8 @@ impl Document {
|
||||||
let mouse_event = MouseEvent::new(
|
let mouse_event = MouseEvent::new(
|
||||||
&self.window,
|
&self.window,
|
||||||
DOMString::from(event_name.as_str()),
|
DOMString::from(event_name.as_str()),
|
||||||
EventBubbles::Bubbles,
|
can_bubble,
|
||||||
EventCancelable::Cancelable,
|
cancelable,
|
||||||
Some(&self.window),
|
Some(&self.window),
|
||||||
0i32,
|
0i32,
|
||||||
client_x,
|
client_x,
|
||||||
|
@ -1376,21 +1384,11 @@ impl Document {
|
||||||
pub fn handle_mouse_move_event(
|
pub fn handle_mouse_move_event(
|
||||||
&self,
|
&self,
|
||||||
js_runtime: *mut JSRuntime,
|
js_runtime: *mut JSRuntime,
|
||||||
client_point: Option<Point2D<f32>>,
|
client_point: Point2D<f32>,
|
||||||
prev_mouse_over_target: &MutNullableDom<Element>,
|
prev_mouse_over_target: &MutNullableDom<Element>,
|
||||||
node_address: Option<UntrustedNodeAddress>,
|
node_address: Option<UntrustedNodeAddress>,
|
||||||
pressed_mouse_buttons: u16,
|
pressed_mouse_buttons: u16,
|
||||||
) {
|
) {
|
||||||
let client_point = match client_point {
|
|
||||||
None => {
|
|
||||||
// If there's no point, there's no target under the mouse
|
|
||||||
// FIXME: dispatch mouseout here. We have no point.
|
|
||||||
prev_mouse_over_target.set(None);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
Some(client_point) => client_point,
|
|
||||||
};
|
|
||||||
|
|
||||||
let maybe_new_target = node_address.and_then(|address| {
|
let maybe_new_target = node_address.and_then(|address| {
|
||||||
let node = unsafe { node::from_untrusted_node_address(js_runtime, address) };
|
let node = unsafe { node::from_untrusted_node_address(js_runtime, address) };
|
||||||
node.inclusive_ancestors(ShadowIncluding::No)
|
node.inclusive_ancestors(ShadowIncluding::No)
|
||||||
|
@ -1398,39 +1396,27 @@ impl Document {
|
||||||
.next()
|
.next()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send mousemove event to topmost target, unless it's an iframe, in which case the
|
|
||||||
// compositor should have also sent an event to the inner document.
|
|
||||||
let new_target = match maybe_new_target {
|
let new_target = match maybe_new_target {
|
||||||
Some(ref target) => target,
|
Some(ref target) => target,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.fire_mouse_event(
|
let target_has_changed = prev_mouse_over_target
|
||||||
client_point,
|
.get()
|
||||||
new_target.upcast(),
|
.as_ref()
|
||||||
FireMouseEventType::Move,
|
.map_or(true, |old_target| old_target != new_target);
|
||||||
pressed_mouse_buttons,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Nothing more to do here, mousemove is sent,
|
|
||||||
// and the element under the mouse hasn't changed.
|
|
||||||
if maybe_new_target == prev_mouse_over_target.get() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let old_target_is_ancestor_of_new_target =
|
|
||||||
match (prev_mouse_over_target.get(), maybe_new_target.as_ref()) {
|
|
||||||
(Some(old_target), Some(new_target)) => old_target
|
|
||||||
.upcast::<Node>()
|
|
||||||
.is_ancestor_of(new_target.upcast::<Node>()),
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Here we know the target has changed, so we must update the state,
|
// Here we know the target has changed, so we must update the state,
|
||||||
// dispatch mouseout to the previous one, mouseover to the new one,
|
// dispatch mouseout to the previous one, mouseover to the new one.
|
||||||
|
if target_has_changed {
|
||||||
|
// Dispatch mouseout and mouseleave to previous target.
|
||||||
if let Some(old_target) = prev_mouse_over_target.get() {
|
if let Some(old_target) = prev_mouse_over_target.get() {
|
||||||
|
let old_target_is_ancestor_of_new_target = old_target
|
||||||
|
.upcast::<Node>()
|
||||||
|
.is_ancestor_of(new_target.upcast::<Node>());
|
||||||
|
|
||||||
// If the old target is an ancestor of the new target, this can be skipped
|
// If the old target is an ancestor of the new target, this can be skipped
|
||||||
// completely, since the node's hover state will be reseted below.
|
// completely, since the node's hover state will be reset below.
|
||||||
if !old_target_is_ancestor_of_new_target {
|
if !old_target_is_ancestor_of_new_target {
|
||||||
for element in old_target
|
for element in old_target
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
|
@ -1442,19 +1428,29 @@ impl Document {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove hover state to old target and its parents
|
|
||||||
self.fire_mouse_event(
|
self.fire_mouse_event(
|
||||||
client_point,
|
client_point,
|
||||||
old_target.upcast(),
|
old_target.upcast(),
|
||||||
FireMouseEventType::Out,
|
FireMouseEventType::Out,
|
||||||
|
EventBubbles::Bubbles,
|
||||||
|
EventCancelable::Cancelable,
|
||||||
pressed_mouse_buttons,
|
pressed_mouse_buttons,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Fire mouseleave here only if the old target is
|
if !old_target_is_ancestor_of_new_target {
|
||||||
// not an ancestor of the new target.
|
let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
|
||||||
|
let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
|
||||||
|
self.handle_mouse_enter_leave_event(
|
||||||
|
client_point,
|
||||||
|
FireMouseEventType::Leave,
|
||||||
|
moving_into,
|
||||||
|
event_target,
|
||||||
|
pressed_mouse_buttons,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref new_target) = maybe_new_target {
|
// Dispatch mouseover and mouseenter to new target.
|
||||||
for element in new_target
|
for element in new_target
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.inclusive_ancestors(ShadowIncluding::No)
|
.inclusive_ancestors(ShadowIncluding::No)
|
||||||
|
@ -1463,26 +1459,99 @@ impl Document {
|
||||||
if element.hover_state() {
|
if element.hover_state() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.set_hover_state(true);
|
element.set_hover_state(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fire_mouse_event(
|
self.fire_mouse_event(
|
||||||
client_point,
|
client_point,
|
||||||
&new_target.upcast(),
|
new_target.upcast(),
|
||||||
FireMouseEventType::Over,
|
FireMouseEventType::Over,
|
||||||
|
EventBubbles::Bubbles,
|
||||||
|
EventCancelable::Cancelable,
|
||||||
pressed_mouse_buttons,
|
pressed_mouse_buttons,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Fire mouseenter here.
|
let moving_from = prev_mouse_over_target
|
||||||
|
.get()
|
||||||
|
.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
|
||||||
|
let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
|
||||||
|
self.handle_mouse_enter_leave_event(
|
||||||
|
client_point,
|
||||||
|
FireMouseEventType::Enter,
|
||||||
|
moving_from,
|
||||||
|
event_target,
|
||||||
|
pressed_mouse_buttons,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the current mouse over target for next frame.
|
// Send mousemove event to topmost target, unless it's an iframe, in which case the
|
||||||
prev_mouse_over_target.set(maybe_new_target.as_deref());
|
// compositor should have also sent an event to the inner document.
|
||||||
|
self.fire_mouse_event(
|
||||||
|
client_point,
|
||||||
|
new_target.upcast(),
|
||||||
|
FireMouseEventType::Move,
|
||||||
|
EventBubbles::Bubbles,
|
||||||
|
EventCancelable::Cancelable,
|
||||||
|
pressed_mouse_buttons,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the target has changed then store the current mouse over target for next frame.
|
||||||
|
if target_has_changed {
|
||||||
|
prev_mouse_over_target.set(maybe_new_target.as_deref());
|
||||||
self.window
|
self.window
|
||||||
.reflow(ReflowGoal::Full, ReflowReason::MouseEvent);
|
.reflow(ReflowGoal::Full, ReflowReason::MouseEvent);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mouse_enter_leave_event(
|
||||||
|
&self,
|
||||||
|
client_point: Point2D<f32>,
|
||||||
|
event_type: FireMouseEventType,
|
||||||
|
related_target: Option<DomRoot<Node>>,
|
||||||
|
event_target: DomRoot<Node>,
|
||||||
|
pressed_mouse_buttons: u16,
|
||||||
|
) {
|
||||||
|
assert!(matches!(
|
||||||
|
event_type,
|
||||||
|
FireMouseEventType::Enter | FireMouseEventType::Leave
|
||||||
|
));
|
||||||
|
|
||||||
|
let common_ancestor = match related_target.as_ref() {
|
||||||
|
Some(related_target) => event_target
|
||||||
|
.common_ancestor(related_target, ShadowIncluding::No)
|
||||||
|
.unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
|
||||||
|
None => DomRoot::from_ref(&*event_target),
|
||||||
|
};
|
||||||
|
|
||||||
|
// We need to create a target chain in case the event target shares
|
||||||
|
// its boundaries with its ancestors.
|
||||||
|
let mut targets = vec![];
|
||||||
|
let mut current = Some(event_target);
|
||||||
|
while let Some(node) = current {
|
||||||
|
if node == common_ancestor {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = node.GetParentNode();
|
||||||
|
targets.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The order for dispatching mouseenter events starts from the topmost
|
||||||
|
// common ancestor of the event target and the related target.
|
||||||
|
if event_type == FireMouseEventType::Enter {
|
||||||
|
targets = targets.into_iter().rev().collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
for target in targets {
|
||||||
|
self.fire_mouse_event(
|
||||||
|
client_point,
|
||||||
|
target.upcast(),
|
||||||
|
event_type,
|
||||||
|
EventBubbles::DoesNotBubble,
|
||||||
|
EventCancelable::NotCancelable,
|
||||||
|
pressed_mouse_buttons,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub fn handle_wheel_event(
|
pub fn handle_wheel_event(
|
||||||
|
|
|
@ -708,16 +708,16 @@ impl Node {
|
||||||
&self,
|
&self,
|
||||||
other: &Node,
|
other: &Node,
|
||||||
shadow_including: ShadowIncluding,
|
shadow_including: ShadowIncluding,
|
||||||
) -> DomRoot<Node> {
|
) -> Option<DomRoot<Node>> {
|
||||||
for ancestor in self.inclusive_ancestors(shadow_including) {
|
for ancestor in self.inclusive_ancestors(shadow_including) {
|
||||||
if other
|
if other
|
||||||
.inclusive_ancestors(shadow_including)
|
.inclusive_ancestors(shadow_including)
|
||||||
.any(|node| node == ancestor)
|
.any(|node| node == ancestor)
|
||||||
{
|
{
|
||||||
return ancestor;
|
return Some(ancestor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreachable!();
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool {
|
pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool {
|
||||||
|
|
|
@ -298,6 +298,7 @@ impl RangeMethods for Range {
|
||||||
fn CommonAncestorContainer(&self) -> DomRoot<Node> {
|
fn CommonAncestorContainer(&self) -> DomRoot<Node> {
|
||||||
self.EndContainer()
|
self.EndContainer()
|
||||||
.common_ancestor(&self.StartContainer(), ShadowIncluding::No)
|
.common_ancestor(&self.StartContainer(), ShadowIncluding::No)
|
||||||
|
.expect("Couldn't find common ancestor container")
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#dom-range-setstart
|
// https://dom.spec.whatwg.org/#dom-range-setstart
|
||||||
|
|
|
@ -552,7 +552,7 @@ pub enum CompositorEvent {
|
||||||
),
|
),
|
||||||
/// The mouse was moved over a point (or was moved out of the recognizable region).
|
/// The mouse was moved over a point (or was moved out of the recognizable region).
|
||||||
MouseMoveEvent(
|
MouseMoveEvent(
|
||||||
Option<Point2D<f32>>,
|
Point2D<f32>,
|
||||||
Option<UntrustedNodeAddress>,
|
Option<UntrustedNodeAddress>,
|
||||||
// Bitmask of MouseButton values representing the currently pressed buttons
|
// Bitmask of MouseButton values representing the currently pressed buttons
|
||||||
u16,
|
u16,
|
||||||
|
|
56
tests/html/test_mouse_events_order_1.html
Normal file
56
tests/html/test_mouse_events_order_1.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Mouse Events Order 1</title>
|
||||||
|
<meta name="specification" content="https://w3c.github.io/uievents/#events-mouseevent-event-order">
|
||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
width: 16ex;
|
||||||
|
height: 16ex;
|
||||||
|
padding: 1ex;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
background: #99DDFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
left: 22ex;
|
||||||
|
background: #44BB99;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p><strong>Description:</strong> Tests the order of mouse events when a pointing device transitions from a
|
||||||
|
visible element A to another visible element B.</p>
|
||||||
|
|
||||||
|
<div id="box-1" class="box blue">A</div>
|
||||||
|
<div id="box-2" class="box green">B</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function print(label, message) {
|
||||||
|
console.log(`${label}: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseEvents(element, label) {
|
||||||
|
element.addEventListener("mouseup", _ => print(label, "mouseup"));
|
||||||
|
element.addEventListener("mousedown", _ => print(label, "mousedown"));
|
||||||
|
element.addEventListener("mouseenter", _ => print(label, "mouseenter"));
|
||||||
|
element.addEventListener("mouseleave", _ => print(label, "mouseleave"));
|
||||||
|
element.addEventListener("mousemove", _ => print(label, "mousemove"));
|
||||||
|
element.addEventListener("mouseout", _ => print(label, "mouseout"));
|
||||||
|
element.addEventListener("mouseover", _ => print(label, "mouseover"));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEvents(document.getElementById("box-1"), "A");
|
||||||
|
handleMouseEvents(document.getElementById("box-2"), "B");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
60
tests/html/test_mouse_events_order_2.html
Normal file
60
tests/html/test_mouse_events_order_2.html
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Mouse Events Order 2</title>
|
||||||
|
<meta name="specification" content="https://w3c.github.io/uievents/#events-mouseevent-event-order">
|
||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
width: 16ex;
|
||||||
|
height: 16ex;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
padding: 1ex;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
background: #99DDFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
width: 10ex;
|
||||||
|
height: 10ex;
|
||||||
|
left: 3ex;
|
||||||
|
top: 3ex;
|
||||||
|
background: #44BB99;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p><strong>Description:</strong> Tests the order of mouse events when a pointing device is moved into an element A,
|
||||||
|
and then into a nested element B and then back out again.</p>
|
||||||
|
|
||||||
|
<div id="box-1" class="box blue">A
|
||||||
|
<div id="box-2" class="box green">B</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function print(label, msg) {
|
||||||
|
console.log(`${label}: ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseEvents(element, label) {
|
||||||
|
element.addEventListener("mouseup", _ => print(label, "mouseup"));
|
||||||
|
element.addEventListener("mousedown", _ => print(label, "mousedown"));
|
||||||
|
element.addEventListener("mouseenter", _ => print(label, "mouseenter"));
|
||||||
|
element.addEventListener("mouseleave", _ => print(label, "mouseleave"));
|
||||||
|
element.addEventListener("mousemove", _ => print(label, "mousemove"));
|
||||||
|
element.addEventListener("mouseout", _ => print(label, "mouseout"));
|
||||||
|
element.addEventListener("mouseover", _ => print(label, "mouseover"));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEvents(document.getElementById("box-1"), "A");
|
||||||
|
handleMouseEvents(document.getElementById("box-2"), "B");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
62
tests/html/test_mouse_events_order_3.html
Normal file
62
tests/html/test_mouse_events_order_3.html
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Mouse Events Order 3</title>
|
||||||
|
<meta name="specification" content="https://w3c.github.io/uievents/#events-mouseevent-event-order">
|
||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
width: 16ex;
|
||||||
|
height: 16ex;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
background: #99DDFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
background: #44BB99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow {
|
||||||
|
background: #EEDD88;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p><strong>Description: </strong> Tests the order of mouse events when a pointing device is moved into a visually
|
||||||
|
overlapped stack of elements having the same dimensions and absolute positions and then moved out again.</p>
|
||||||
|
|
||||||
|
<div id="box-1" class="box blue">A
|
||||||
|
<div id="box-2" class="box green">B
|
||||||
|
<div id="box-3" class="box yellow">C</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function print(label, msg) {
|
||||||
|
console.log(`${label}: ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseEvents(element, label) {
|
||||||
|
element.addEventListener("mouseup", _ => print(label, "mouseup"));
|
||||||
|
element.addEventListener("mousedown", _ => print(label, "mousedown"));
|
||||||
|
element.addEventListener("mouseenter", _ => print(label, "mouseenter"));
|
||||||
|
element.addEventListener("mouseleave", _ => print(label, "mouseleave"));
|
||||||
|
element.addEventListener("mousemove", _ => print(label, "mousemove"));
|
||||||
|
element.addEventListener("mouseout", _ => print(label, "mouseout"));
|
||||||
|
element.addEventListener("mouseover", _ => print(label, "mouseover"));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEvents(document.getElementById("box-1"), "A");
|
||||||
|
handleMouseEvents(document.getElementById("box-2"), "B");
|
||||||
|
handleMouseEvents(document.getElementById("box-3"), "C");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue