Fix wpt failures in pointerevent_after_target_removed.html
Categories
(Core :: DOM: UI Events & Focus Handling, defect)
Tracking
()
People
(Reporter: hsinyi, Assigned: masayuki)
References
Details
Attachments
(1 file)
We currently fail at https://wpt.fyi/results/pointerevents/pointerevent_after_target_removed.html%3Fmouse
| Assignee | ||
Comment 1•3 years ago
|
||
Hmm, I'm not sure whether the test is valid because all tests fail in any browsers.
The test expects that mouse events in parent should be fired in the following order in the parent when removing click target:
mouseentermouseovermouseoutmouseleave
However, both Chrome and Firefox fire them in this order:
mouseentermouseovermouseentermouseoutmouseleave
The 3rd mouseenter is redundant. The second one is caused by a default action of synthesized mouse move in this stack:
#02: mozilla::EnterLeaveDispatcher::Dispatch (M:\src\dom\events\EventStateManager.cpp:4649)
#03: mozilla::EventStateManager::NotifyMouseOver (M:\src\dom\events\EventStateManager.cpp:4802)
#04: mozilla::EventStateManager::GenerateMouseEnterExit (M:\src\dom\events\EventStateManager.cpp:4939)
#05: mozilla::EventStateManager::PreHandleEvent (M:\src\dom\events\EventStateManager.cpp:849)
#06: mozilla::PresShell::EventHandler::DispatchEvent (M:\src\layout\base\PresShell.cpp:8211)
#07: mozilla::PresShell::EventHandler::HandleEventWithCurrentEventInfo (M:\src\layout\base\PresShell.cpp:8183)
#08: mozilla::PresShell::EventHandler::HandleEventUsingCoordinates (M:\src\layout\base\PresShell.cpp:7129)
#09: mozilla::PresShell::EventHandler::HandleEvent (M:\src\layout\base\PresShell.cpp:6927)
#10: mozilla::PresShell::HandleEvent (M:\src\layout\base\PresShell.cpp:6870)
#11: nsViewManager::DispatchEvent (M:\src\view\nsViewManager.cpp:652)
#12: mozilla::PresShell::DispatchSynthMouseMove (M:\src\layout\base\PresShell.cpp:3858)
#13: mozilla::PresShell::ProcessSynthMouseMoveEvent (M:\src\layout\base\PresShell.cpp:5835)
#14: mozilla::PresShell::nsSynthMouseMoveEvent::WillRefresh (M:\fx64-dbg\dist\include\mozilla\PresShell.h:2034)
#15: nsRefreshDriver::Tick (M:\src\layout\base\nsRefreshDriver.cpp:2582)
I think that it's reasonable to synthesize the mousemove is reasonable for users. On the other hand, I'm not sure about mouseenter. Once the element which is set to mLastOverElement (in the test, it's the div#child), but it's cleared when it's removed from the tree. Therefore, EventStateManager cannot check whether the cursor has already been in div#parent.
Edgar, do you have any ideas? I don't know it's reasonable that we should keep storing another element at removing mLastOverElement since it requires a hittest at mutation. So, dispatching mouseenter in this case is reasonable from both simpler code and the performance points of view.
| Assignee | ||
Comment 2•3 years ago
|
||
I got the reason why the pointer events tests gets only pointerenter. At removing the element, eMouseMove event is synthesized but not ePointerMove. Therefore, EventStateManager does not handle pointerout etc. I'll post a patch for this.
| Assignee | ||
Comment 3•2 years ago
|
||
I found a bug of the pointer events tests (the expectation conflicts with another test one). I filed an issue for that.
| Assignee | ||
Comment 4•2 years ago
|
||
The test result conflicts with the test added in bug 1529904.
| Assignee | ||
Comment 5•2 years ago
|
||
My draft patch is here: https://hg.mozilla.org/try/rev/981166f3400a038f104a9d93ab322d506adc6fc5
Comment 6•2 years ago
•
|
||
(In reply to Masayuki Nakano [:masayuki] (he/him)(JST, +0900) from comment #1)
Once the element which is set to
mLastOverElement(in the test, it's thediv#child), but it's cleared when it's removed from the tree. Therefore,EventStateManagercannot check whether the cursor has already been indiv#parent.Edgar, do you have any ideas? I don't know it's reasonable that we should keep storing another element at removing
mLastOverElementsince it requires a hittest at mutation. So, dispatchingmouseenterin this case is reasonable from both simpler code and the performance points of view.
I looks into spec again and run https://codepen.io/edgarchen-the-decoder/pen/jOQxrvp on various browsers,
For pointerenter, spec says:
The user agent MUST fire a pointer event named pointerenter when a pointing device is moved
into the hit test boundaries of an element or one of its descendants,
and for pointerleave, spec says:
The pointing device is moved out of the hit test boundaries of an element and all of its descendants.
When pointer device is moved to a child element, pointerenter is fired to parent element even if pointer device is not actually "enter" it (from the hittest point of view). So after a child element is removed, it seems reasonable to use its parent to generate corresponding enter/leave event when next hittest happens.
And for pointerout, since both Chrome and Safari fire pointerout event on the element which is removed and spec doesn't say we should suppress when the element is not connected, so we probably should not clear mLastOverElement when it is removed from tree.
About the event timing, per https://github.com/web-platform-tests/interop/issues/380#issuecomment-1639616890, it seems we should generate event when "real" movement is occurred, Safari behave like this also on mouse event.
Does this sound reasonable?
| Assignee | ||
Comment 7•2 years ago
|
||
(In reply to Edgar Chen [:edgar] from comment #6)
When pointer device is moved to a child element,
pointerenteris fired to parent element even if pointer device is not actually "enter" it (from the hittest point of view). So after a child element is removed, it seems reasonable to use its parent to generate corresponding enter/leave event when next hittest happens.
Yeah, I got it, thank you for explaining that.
And for
pointerout, since both Chrome and Safari firepointeroutevent on the element which is removed and spec doesn't say we should suppress when the element is not connected, so we probably should not clearmLastOverElementwhen it is removed from tree.
Oh, really? I don't see pointerleave nor pointerout before pointerenter and pointerover when I click the red box in Chrome on Windows/macOS either over green or black box. So, it seems that Chrome just clears the last enter/over element like us, but set it again with the synthetic pointer move.
On the other hand, yeah, Safari works as you expected. However, pointerleave nor pointerout is not fired on the div#parent (probably because of disconnected target in the former). Therefore, the test anyway fails in Safari.
| Assignee | ||
Comment 8•2 years ago
|
||
I updated the issue too. I think that it's fine to dispatch mLastOverElement even after it's disconnected. However, it's hard to store all event targets of the preceding pointerenter so that it's hard to dispatch pointerout and pointerleave on parent (note that the test listens to event on parent). Therefore, it seems reasonable that only pointerenter fired on the parent (current Gecko and Safari's result).
Additionally, the test should check the events in the child too.
Anyway, I think that the spec should define this case first.
| Assignee | ||
Updated•2 years ago
|
Comment 9•2 years ago
|
||
(In reply to Masayuki Nakano [:masayuki] (he/him)(JST, +0900) from comment #7)
Oh, really? I don't see
pointerleavenorpointeroutbeforepointerenterandpointeroverwhen I click the red box in Chrome on Windows/macOS either over green or black box. So, it seems that Chrome just clears the last enter/over element like us, but set it again with the synthetic pointer move.
Ah, my bad, you are right, Chrome doesn't fire pointerout and pointerleave event on removed element, only Safari fires. (I updated comment#6)
Comment 10•2 years ago
|
||
(In reply to Masayuki Nakano [:masayuki] (he/him)(JST, +0900) from comment #8)
I updated the issue too. I think that it's fine to dispatch
mLastOverElementeven after it's disconnected. However, it's hard to store all event targets of the precedingpointerenterso that it's hard to dispatchpointeroutandpointerleaveon parent (note that the test listens to event on parent). Therefore, it seems reasonable that onlypointerenterfired on the parent (current Gecko and Safari's result).
I think it is reasonable that parent doesn't receive pointerout event, as pointerout is only fired on removed element and unable to bubble to its parent.
For pointerenter and pointerleave, it seems we need to store all element in the parent chain of mLastOverElement and compare to the new list which is built from new mLastOverElement, and generate pointerenter and pointerleave events based on the differences of two lists.
Additionally, the test should check the events in the child too.
Anyway, I think that the spec should define this case first.
It would be good if spec can have clear steps on how to handle pointerenter and pointerleave.
| Assignee | ||
Updated•2 years ago
|
Comment 11•2 years ago
|
||
It seems that a proposed fix is on the spec issue now. Perhaps it's worth trying it out?
| Assignee | ||
Comment 12•2 years ago
•
|
||
Although I need several days to be back here.
Current our result:
pointerover@childpointerenter@parentpointerenter@childpointerdown@child,- (child-removed)
pointerup@parentpointerover@parentpointerenter@parentpointerdown@parentpointerup@parentpointerout@parentpointerleave@parent
Expected result is:
pointerover@childpointerenter@parentpointerenter@childpointerdown@child- (child-removed)
pointerup@parentpointerdown@parentpointerup@parentpointerout@parentpointerleave@parent
So, unnecessary pointerover and pointerenter are fired on the parent.
Chrome does:
pointerover@childpointerenter@parentpointerenter@childpointerdown@child- (child-removed)
pointerover@parentpointerenter@parentpointerup@parentpointerdown@parentpointerup@parentpointerout@parentpointerleave@parent
Similar to Gecko, but the unnecessary events are fired before pointerup.
Safari does:
pointerover@childpointerenter@parentpointerenter@childpointerdown@child- (child-removed)
pointerup@parentpointerout@childpointerleave@childpointerover@parentpointerenter@parentpointerdown@parentpointerup@parentpointerout@parentpointerleave@parent
Safari works similar to Gecko but dispatch pointerout and pointerleave on the removed node too.
| Assignee | ||
Comment 13•2 years ago
|
||
Oh, and also about mouse events:
Gecko:
mouseover@childmouseenter@parentmouseenter@child- (child-removed)
mouseover@parentmouseenter@parentmouseup@parentmousedown@parentmouseup@parentmouseout@parentmouseleave@parent
Expectation:
mouseover@childmouseenter@parentmouseenter@child- (child-removed)
mouseup@parentmousedown@parentmouseup@parentmouseout@parentmouseleave@parent
I feel the expectation does not match with UI Events definition because UI Events defines the events should be fired when:
mouseleave: "also dispatch this event when the element or one of its descendants moves to be no longer underneath the primary pointing device"mouseover: "or when the element is moved to be underneath the primary pointing device"mouseout: "or when the element is moved to be no longer underneath the primary pointing device"
UI Events uses "element" as subject of the sentence. Therefore, it's unclear in the cases when the element is moved/removed without the cursor move.
| Assignee | ||
Comment 14•2 years ago
|
||
So, about Pointer Events, all browsers currently dispatch at least pointerover and pointerenter on the ex-parent of the removed node. However, the WPT expects which should be stopped. So, this could break web apps in the wild.
Smaug, Edgar: WDYT? I think that this is a risky change and we should not do this at least first of the browser vendors due to the market share.
Comment 15•2 years ago
|
||
oh, that part is not covered by https://github.com/w3c/pointerevents/issues/477#issuecomment-1684634001
I mean that comment is about what happens afterwards.
Could you file a spec issue (assuming there isn't an issue for this already)?
https://github.com/w3c/pointerevents/issues/477 is about *out and *leave events but looks like we the question also about *over and *enter.
Or perhaps it is fine to continue discussion in that same spec issue, but change the title a bit to cover also over and enter events or something like that?
| Assignee | ||
Comment 16•2 years ago
|
||
(I'll try to do it in this week, sorry for the delay.)
| Assignee | ||
Comment 17•2 years ago
|
||
Filed a UI Events issue about mouse* events.
| Assignee | ||
Comment 18•2 years ago
|
||
I added comments for the current result (here and below).
There are still several issues:
- All browsers should store all elements fired
mouseenterevents instead of the deepest descendant and stop dispatchingmouseeneteron the parent of the removing element. - It seems that all browsers have a performance hack for synthesizing
mouseover,mouseout,mouseenter,mouseleave,pointerover,pointerout,pointeenterandpointerleave. Therefore, ifpointerdownremoves the target,mouseoverandpointerovermay be dispatched either before or afterpointerup. - Only Chrome dispatches a fallback
mousedownormouseupto the parent element of removed element which has already been removed by the precedingpointerdownorpointerup. I think that this behavior is not reasonable because users may not expect the click in the parent. However, the test expects this behavior.
So, currently, we could work on changing the mouseenter/mouseleave target management. However, current behavior is compatible with the other browsers. Therefore, "correcting" the behavior may have some risk.
Smaug and/or Edgar, what do you think? Should I change the code right now? Or after the other browsers fix their behavior?
Comment 19•2 years ago
|
||
I think I don't understand the first point.
I don't understand what the performance hack is. It is about behavior, not about performance. I think that is fine. moves and down/up aren't really connected to each others.
And Chrome's behavior on down/up and then using ancestor if the target was removed seems quite reasonable. That way the page doesn't lose any down/up events.
| Assignee | ||
Comment 20•2 years ago
|
||
(In reply to Olli Pettay [:smaug][bugs@pettay.fi] from comment #19)
I don't understand what the performance hack is. It is about behavior, not about performance.
In my understanding, it's for performance and ensuring "safe to run script" to synthesize fake mousemove if mouseover'ed element is changed by the DOM mutation or style changes since multiple mutations and/or style changes may occur under the mouse cursor in a tick. Therefore, without the asynchronous dispatching, the changes under the mouse cursor is slower than the cases when the mouse cursor is outside the element.
I think that is fine. moves and down/up aren't really connected to each others.
I hope so, but I think that it should be mentioned in the spec. I guess somebody would file the behavior as a bug of browsers.
And Chrome's behavior on down/up and then using ancestor if the target was removed seems quite reasonable. That way the page doesn't lose any down/up events.
Oh, really? The preceding pointerdown event has already been fired on the removed target. I think it's odd that a fallback mousedown event is fired on different element. Especially, the events insinuate that the user tries to execute something of the removed target element. However, that may cause running something of the bigger/outer target of the element which the user actually clicked.
I wonder, there should be inconsistent results between pointerdown and mousedown:
https://jsfiddle.net/d_toybox/m18jpnae/
e.addEventListener("pointerdown", event => {
event.target.remove();
});
addEventListener("click", () => alert("clicked"));
https://jsfiddle.net/d_toybox/m18jpnae/1/
e.addEventListener("mousedown", event => {
event.target.remove();
});
addEventListener("click", () => alert("clicked"));
In the first case, mousedown will be fired on the parent, then, mouseup will be fired on the parent too. Therefore, I expect that click event is fired on it.
In the later case, mousedown is fired on the child, then, mouseup will be fired on the parent. Therefore, click event should not be fired on the parent due to different event targets.
However, Chrome does not dispatch click in either cases. So, it seems that Chrome fixes the candidate of the click event target before dispatching pointerdown. However, click is defined by UI Events and Pointer Events is a different spec. So I feel it's odd that Chrome does not dispatch it in the previous case.
Actually, we fix it at dispatching mousedown:
Comment 21•2 years ago
|
||
If mousedown isn't dispatched when target is removed in pointerdown, then the problem is that should also we also not dispatch mouseup.
It is confusing if the page gets pointerdown, (but not mousedown), then pointerup and mouseup.
And click is yes possibly problematic (but click targeting has other issues too, like https://github.com/w3c/pointerevents/issues/356, which isn't fixed yet in browsers, I think, even though the spec has been clarified.)
| Assignee | ||
Comment 22•2 years ago
•
|
||
(In reply to Olli Pettay [:smaug][bugs@pettay.fi] from comment #21)
If mousedown isn't dispatched when target is removed in pointerdown, then the problem is that should also we also not dispatch mouseup.
I don't think so. mouseup is a physical device state change event. Therefore not dispatching click event which is a logical event for the target should be enough.
Filed: https://github.com/w3c/pointerevents/issues/492
It is confusing if the page gets pointerdown, (but not mousedown), then pointerup and mouseup.
I think that Pointer Events spec assumes (and maybe recommends) that web apps which does something in pointerdown should prevent its default to stop dispatching the legacy events. However, the testcase does not do that. Therefore, I think this is a tricky case for the spec editors.
| Assignee | ||
Updated•2 years ago
|
| Assignee | ||
Comment 23•2 years ago
|
||
pointerup should not imply the pointer move and pointerover should be fired
only when the pointer is actually moved. Therefore, pointerover should be
fired after pointerup which is followed by another pointermove instead of
before pointerup.
Depends on D199469
Updated•2 years ago
|
Updated•2 years ago
|
Comment 24•2 years ago
|
||
Comment 26•2 years ago
|
||
Backed out for causing wpt failures on mouse_boundary_events_after_removing_last_over_element.html
Comment 27•2 years ago
|
||
Huh, how a change to a test caused issues in another test.
| Reporter | ||
Comment 28•2 years ago
|
||
(In reply to Olli Pettay [:smaug][bugs@pettay.fi] from comment #27)
Huh, how a change to a test caused issues in another test.
Patches of bug 1875424 got backed out due to the same test failure. Bug 1875424 seemed to be the real regressor.
Comment 31•2 years ago
|
||
Comment 32•2 years ago
|
||
| bugherder | ||
| Assignee | ||
Comment 33•2 years ago
|
||
This was backed out in the upstream.
| Assignee | ||
Comment 34•2 years ago
|
||
Will be fixed in bug 1888013 (D206209).
Comment 35•1 year ago
|
||
All the tests under pointerevent_after_target_removed.html?mouse pass after bug 1888013.
Description
•