Skip to content

Popovers are broken when shown in a contextmenu event listener #10905

Open

Description

@jpzwarte

What is the issue with the HTML Standard?

https://codepen.io/jpzwarte/pen/OPLQypz?editors=1010

If you want to use a popover for a context menu, you cannot use the contextmenu event to display the popover. The problem is that the popover is immediately light dismissed.

(not sure if this belongs in whatwg/html or whatwg/dom)

Activity

  1. jpzwarte commented on Jan 9, 2025

    @jpzwarte
    Author

    Perhaps the light dismiss behavior should be tweaked to not close any popovers that are shown immediately before in contextmenu, mousedown, mouseup, pointerdown, pointerup, touchstart, touchend event listeners?

  2. mkrause commented on Jan 10, 2025

    @mkrause

    Maybe wrap it in a setTimeout(fn, 0) so the popover gets shown after the event.

  3. jpzwarte commented on Jan 10, 2025

    @jpzwarte
    Author

    That doesn't work in Chrome, FF or Safari. Once you increase the timeout to at least 100ms, then it sometimes works. But at that point you start to have a noticeable delay.

  4. mkrause commented on Jan 10, 2025

    @mkrause

    That doesn't work in Chrome, FF or Safari. Once you increase the timeout to at least 100ms, then it sometimes works. But at that point you start to have a noticeable delay.

    Ah, interesting. I just tested it, the reason is because contextmenu is fired on mouse down, but on mouse up the popover will be closed.

    To test this, try wrapping the showPopover() in setTimeout(fn, 0), and then start right clicking but hold the click. The popover will show. As soon as you release the click the popover is closed.

    What's also interesting is it doesn't seem that the "close popovers on click/mouseup" can be canceled. At least no combination that I tried with canceling click/mouseup/pointerup resulted in canceling the popover close. So that doesn't seem like a viable option either.

    popover="manual" would work of course, but then you have to reimplement light dismiss.

  5. mkrause commented on Jan 10, 2025

    @mkrause

    Found a workaround by using mouseup instead and checking for right click (or CTRL+click on Mac).

    document.querySelector('span')?.addEventListener('contextmenu', (event) => {
      event.preventDefault();
    });
    
    
    document.querySelector('span')?.addEventListener('mouseup', (event) => {
      if (event.which === 3 || event.ctrlKey) {
        document.querySelector('[popover]').showPopover();   
      }
    });

    No setTimeout necessary when done in mouseup. Tested it in Chrome, Firefox, and Safari and works in all of them.

  6. annevk commented on Jan 16, 2025

    @annevk
    Member

    This sounds similar to #10890 (comment).

    cc @josepharhar @nt1m @mfreed7

  7. josepharhar commented on Jan 22, 2025

    @josepharhar
    Contributor

    Yeah, we are running light dismiss kind of unconditionally whenever there is a pointerup event. Maybe we should add a flag for the case that a popover is shown in between pointerdown and pointerup and skip doing light dismiss in that case? I don't think that would help with #10890 (comment) though.

  8. mfreed7 commented on Jan 23, 2025

    @mfreed7
    Contributor

    Interesting bug!

    Yeah, we are running light dismiss kind of unconditionally whenever there is a pointerup event. Maybe we should add a flag for the case that a popover is shown in between pointerdown and pointerup and skip doing light dismiss in that case?

    Yeah, this sounds like a good solution to me. Might be tricky to keep track of which popover(s) were open, but we can probably do something. Does this sound like a reasonable solution to others?

    I don't think that would help with #10890 (comment) though.

    Perhaps it could, basically if we keep track of the list of open popovers during pointerdown, and compare to pointerup, and exempt any popovers that changed (either direction) from the light dismiss behavior?

  9. nt1m commented on Jan 23, 2025

    @nt1m
    Member

    Another solution is to check for the event button of the pointerup event? (Just to be clear, I don't have a strong preference on how to resolve this, just throwing options out there)

  10. mfreed7 commented on Jan 23, 2025

    @mfreed7
    Contributor

    Another solution is to check for the event button of the pointerup event? (Just to be clear, I don't have a strong preference on how to resolve this, just throwing options out there)

    Sounds interesting, but help me understand. In the repro for this issue, the pointerdown and pointerup both happen on the same <span>, don't they?

  11. nt1m commented on Jan 24, 2025

    @nt1m
    Member

    If you right click, there should be a different event.button used right? Why should right click trigger light dismiss in the first place?

  12. jpzwarte commented on Jan 24, 2025

    @jpzwarte
    Author

    Sounds interesting, but help me understand. In the repro for this issue, the pointerdown and pointerup both happen on the same <span>, don't they?

    If you mousedown over the span, move the mouse elsewhere and then release when it's not over the span, it still light dismisses.

    Aside from technical questions: a common way to invoke a context menu item is to mousedown, hover over a specific menu item and the release.

  13. mfreed7 commented on Jan 25, 2025

    @mfreed7
    Contributor

    If you right click, there should be a different event.button used right? Why should right click trigger light dismiss in the first place?

    Ahh, mouse "button", not "the <button>". Thanks. I think normal behavior for transient things is that any mouse click, regardless of the button being pressed, light-dismisses popovers. (Unless the right-click is on the popover, of course.) For example, the UA-provided context menu itself is roughly a popover, and if you right-click somewhere else, the first one closes.

    If you mousedown over the span, move the mouse elsewhere and then release when it's not over the span, it still light dismisses.

    You mean right-mouse-down, etc.? Yeah, mouse drags also (per spec) light dismiss the popover, unless the drag starts or ends on the popover. That's so that you can e.g. select text on the popover.

    Aside from technical questions: a common way to invoke a context menu item is to mousedown, hover over a specific menu item and the release.

    That is common, at least on Mac platforms. I do think we should work on a solution to this issue. I'd love to make that solution also fix #10890 (comment).

  14. webstrand commented on Mar 11, 2025

    @webstrand

    I needed the contextmenu event to immediately open the popover, so that menu items could be selected on mouseup like native context menus. So I open the popover in manual mode and then wait for a pointerup, pointerleave, or pointercancel to switch to auto mode.

    The downside to this approach is that changing el.popover="auto" also closes the popover (and that event can't be cancelled either). So you have to re-open the popover immediately after and ignore any spurious beforetoggle and toggle events.

  15. josepharhar commented on Mar 13, 2025

    @josepharhar
    Contributor

    What if we set the popover pointerdown target to this element at the end of the show popover steps? I tested it out locally and it fixes the repro in the OP

  16. jpzwarte commented on Mar 14, 2025

    @jpzwarte
    Author

    What if we set the popover pointerdown target to this element at the end of the show popover steps? I tested it out locally and it fixes the repro in the OP

    That sounds like a plan? Guess we would need some WPTs to cover this? This would mean a spec change as well?

    I assume this covers touchstart & mousedown as well?

  17. josepharhar commented on Mar 15, 2025

    @josepharhar
    Contributor

    That sounds like a plan? Guess we would need some WPTs to cover this? This would mean a spec change as well?

    Yes, yes, and yes

    I assume this covers touchstart & mousedown as well?

    I think so, but what touch gesture on which platform would you use to open a context menu? Want to make sure I'm not missing anything.

  18. jpzwarte commented on Mar 15, 2025

    @jpzwarte
    Author

    I think so, but what touch gesture on which platform would you use to open a context menu? Want to make sure I'm not missing anything.

    I would think doing a long press (that starts with a touchstart) on iOS would trigger the contextmenu event, but it seems it doesn't? https://bugs.webkit.org/show_bug.cgi?id=213953 The last comment implies it works like that on Android at least.

    But from reading https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerdown_event, i think pointerdown covers both mousedown and touchstart. Long press not triggering contextmenu is probably out of scope of this issue.

  19. josepharhar commented on Apr 1, 2025

    @josepharhar
    Contributor

    I'm going to try out looking at event.button in popover light dismiss to consider not running light dismiss.

  20. josepharhar commented on Apr 3, 2025

    @josepharhar
    Contributor

    I created a PR to fix this by looking at event.button as I mentioned my last comment: #11185

  21. josepharhar commented on Jun 30, 2025

    @josepharhar
    Contributor

    My prototype fix for w3c/pointerevents#542 seems to fix this, so #11185 might not be necessary

  22. nuxodin commented on Sep 23, 2026

    @nuxodin

    A workaround that works today, for anyone hitting this:

    el.addEventListener('contextmenu', e => {
        e.preventDefault();
        popover.showPopover();
        // pointerup is still pending on Linux/macOS: capture it, so it doesn't light-dismiss the popover
        if (e.buttons) popover.setPointerCapture(e.pointerId);
    });

    On Linux and macOS, contextmenu fires between pointerdown and pointerup. With pointer capture, the pending pointerup targets the popover. Because the pointerdown and pointerup targets now differ, light dismiss doesn't happen.

    e.buttons is 0 when there is no pending pointerup, so nothing is captured in those cases:

    • On Windows, contextmenu fires after pointerup.
    • A menu opened from the keyboard has no pointer involved.

    Tested with real input events:

    • Firefox 156 (Linux): broken without the workaround, works with it.
    • Chrome 154 (Linux): already works without it (probably because of the click-based light dismiss from Change light dismiss to use click events #11536). The workaround doesn't break anything there.

    In both browsers, clicking a menu item and clicking outside still behave as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: popoverThe popover attribute and friends

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions