2

I'm currently using Vaadin Flow version 14 (https://github.com/vaadin/platform/releases/tag/14.0.0)
I run Java version 1.8.0_231, 64 bit.

I simply want to be able to detect (in java!) whenever a user does either of these actions:

  • Closes the current browser tab (or their browser)
  • Clicks F5 on their keyboard, or pressed the Refresh button in their browser
  • Clicks a link (or anything else), which redirects them away from my website

I've tried lots of different things to detect this. The only thing I have been able to detect so far, is whenever the current VaadinSession expires (which I can change by doing VaadinSession.getCurrent().getSession().setMaxInactiveInterval(15)). This makes every session expire after 15 seconds (15 is just a testing number in my case).



With Vaadin 8, I believe you can just do this and it will work out of the box:

        JavaScript.getCurrent().execute(
            "function closeListener() { catchClose(); } " +
                    "window.addEventListener('beforeunload', closeListener); " +
                    "window.addEventListener('unload', closeListener);"
        );
        JavaScript.getCurrent().addFunction("catchClose", arguments ->
        {
            System.out.println("user has quit :o");
        });

I can't use this tho, since I use Vaadin 14, not 8.


I have tried adding this to my View class:

    @Override
    protected void onDetach(DetachEvent detachEvent)
    {
        System.out.println("onDetach " + detachEvent);
    }

It only prints this message when the session expires (15 seconds in my case). Even if I don't close the page.


I have tried implementing BeforeLeaveObserver / BeforeLeaveListener and overridding this:

    @Override
    public void beforeLeave(BeforeLeaveEvent beforeLeaveEvent)
    {
        System.out.println("beforeLeave");
    }

This never prints.


I've also tried all of these, but they do not do what I need:

        VaadinResponse.getCurrent().getService().addUIInitListener(e ->
        {
            System.out.println("1 addUIInitListener : " + e);
            e.getUI().addBeforeLeaveListener(e2 ->
            {
                System.out.println("1.1 addBeforeLeaveListener : " + e2);
            });
            e.getUI().addDetachListener(e2 ->
            {
                System.out.println("1.2 addDetachListener : " + e2);
            });
        });
        VaadinResponse.getCurrent().getService().addServiceDestroyListener(e ->
        {
            System.out.println("2 addServiceDestroyListener : " + e);
        });
        VaadinResponse.getCurrent().getService().addSessionDestroyListener(e ->
        {
            System.out.println("3 addSessionDestroyListener : " + e);
        });

1 addUIInitListener gets printed when the user loads the page.
1.1 addBeforeLeaveListener never prints.
2 addServiceDestroyListener never prints. A Service isn't the same as a Session. Makes sense tho.
1.2 and 3 addSessionDestroyListener prints wafter awhile. Takes like 15-30 seconds tho.


Is there not a way to detect this basically instantly? :/

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
HoverCatz
  • 65
  • 1
  • 5
  • 19

3 Answers3

3

The JavaScript approach to detect closing still works.

The syntax just has changed a little

UI.getCurrent().getPage().executeJs("function closeListener() { $0.$server.windowClosed(); } " +
        "window.addEventListener('beforeunload', closeListener); " +
        "window.addEventListener('unload', closeListener);",getElement());


@ClientCallable
public void windowClosed() {
    System.out.println("Window closed");
}

Although this is not 100% bullet proof way, since I think not the all browsers work the same way. For example the above code triggers twice with Chrome, where listening just beforeunload is enough.

This allows to simplify the JavaScript to

    UI.getCurrent().getPage().executeJs(
"window.addEventListener('beforeunload', () => $0.$server.windowClosed()); ",getElement());

BeforeLeaveObserver works with navigation. So if you use RouterLink or call ui.navigate("route") then BeforeLeaveEvent is dispatched, but not when you call page.setLocation().

Browser can close due crash, or lost due other reasons, then beforeunload naturally does not happen. The last resort is the heart beat based detection mechanism. I.e. after three lost heart beats Vaadin server will conclude that Browser was lost. There is naturally a delay in this, which simply cannot be avoided.

Tatu Lund
  • 9,949
  • 1
  • 12
  • 26
  • Thanks, I'll try this. Too bad this isn't documented. I've seriously been googling for days :( – HoverCatz Feb 06 '20 at 14:14
  • Its unfortunate if this documentation was difficult to find https://vaadin.com/docs/v14/flow/element-api/client-server-rpc.html – Tatu Lund Feb 07 '20 at 08:24
  • This doesn't seem to detect when I close tabs in Chrome. Maybe I'm thinking about this all wrong? I want to limit each session (each user) to one (1) active tab at once. – HoverCatz Feb 15 '20 at 21:46
  • If you want to limit user to have only one tab open, I think there is no need to observe closing of the Browser window. Instead you can at opening of the view check VaadinSession.getCurrent().getUIs() and if the size is more than one, close the UI. – Tatu Lund Feb 17 '20 at 13:14
  • Each time you refresh the page, that list increases by one (1), or two (2) if you have enabled @Push. The old ones never disappears. I accidentally left my browser tab open for a few hours, having it refresh once every minute, the list had several thousand UIs. – HoverCatz Feb 17 '20 at 21:21
1

Depending on which browsers you need to support, you might also want to take a look at the Beacon API, which is supported by all modern browsers, but not IE11.

The Beacon API has the benefit of being non-blocking. With the unload listener, the client waits for a response before closing the tab, which might be bad user experience.

Still, as Tatu mentioned, if the browser crashes or the user loses internet connection, all you can do is wait for the session to time out.

Erik Lumme
  • 5,202
  • 13
  • 27
  • 1
    Actually, the three missed heartbeats won't work in the case the browser crashes. The way heartbeat works is that in the case that there are multiple UIs open in the same VaadinSession (multiple browser tabs, in the same browser window, open on the same app), a UI with three missed heartbeats will be detached when a request comes to the same VaadinSession. In other words: close one tab out of three, it misses 3 heartbeats, another tab gets a request, closed-tab-UI is detached. Otherwise the UI will be detached after session timeout. – ollitietavainen Feb 06 '20 at 06:55
0

Solution for Vaadin Flow 22. I placed into index.html a section right after </style> closing tag:

  <script>
      window.addEventListener('beforeunload', function (event) {
       event.returnValue = 'Are you sure you want to close the application?';
      });
  </script>

Most likely, you won't see your custom message in the dialog window - you'll see the default one. I didn't see it either, but still apply it ;)

dimirsen Z
  • 873
  • 13
  • 16