Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

click event is registered delayed on components opened in new browser windows #3933

Closed
danielSt-dev opened this issue Jun 10, 2021 · 9 comments
Labels
🐞 bug Something isn't working 🔩 p2-edge-case

Comments

@danielSt-dev
Copy link

danielSt-dev commented Jun 10, 2021

Version

3.1.1

Reproduction link

https://codesandbox.io/s/determined-yonath-3hccx

Steps to reproduce

  1. open child window with a click on "open,close window" for the first time and wait 3 seconds
  2. click on message --> nothing happens
  3. wait 3 seconds and click again --> nothing happens
  4. wait another 15 seconds and click again --> alert message in "main" window appears (chrome: if devtools are closed, a dot on the tab of main window notifies you, that an alert is there)

So you see, there is a relatively long time until the click handler will be registered (about 15-20 seconds)

Now close the window with a click on "open,close window" in main window.

If you repeat the above steps, the click handler will only be called after a few minutes (1-2 minutes; if it won't work, leave it open and try it after 5-10 minutes).
With every close/open new window the time increases.
Important: Do not click too often after opening the window! It seems that the click eventhandler never gets registered if you click too often.

Info: firefox appears to be faster than chrome. After repeating this 4 times firefox needs about 3 minute that the click event is handled. chrome much more.

What is expected?

click events should be fired/registered immediately

What is actually happening?

it seems the click event will be registered delayed.


I found have a problem with click events on components, that are opened in a new browser window with vue 3.
I open a vue 3 component in a new window like the following code (copied and customized from https://github.com/Shamus03/vue-window-portal):

<template>
    <teleport to="body">
        <div v-if="open" v-show="windowLoaded" ref="teleportingContent">
            <slot></slot>
        </div>
    </teleport>
</template>
...
methods: {
  openPortal() {
    this.windowRef = window.open('', '', '');
    this.windowRef.document.body.innerHTML = '';
    const app = document.createElement('div');
    app.id = 'app';
    app.appendChild(this.$refs.teleportingContent);
    this.windowRef.document.body.appendChild(app);
  }
}
...

With this code the slot component will be attached to the DOM of the new opened window. This works and the vue objects/props are reactive in the new window.

The problem is if there is a click-eventhandler on an element, which is attached to DOM of the new window. It seems that the click event is registered delayed.

If the window is closed/opened multiple times, the time of the click registration increases every time.

I want to be able to "dock off" some content of the main window to a new window (e. g. from a video conference push two video participants to new windows; move a component to a new window for comparing content in two screens).

@lidlanca
Copy link
Contributor

lidlanca commented Jun 11, 2021

bug related to fix made for vuejs/vue#6566

https://github.com/vuejs/vue-next/blob/870f2a7ba35245fd8c008d2ff666ea130a7e4704/packages/runtime-dom/src/modules/events.ts#L118-L128

The issue is, that for browsers where the event uses the hi-res timestamp
The child window time resets/start from 0, when the new window is opened, creating the discrepancy
events triggered in the child will have a lower timestamp than the parent window.

The event handler is invoked only when the child window internal time catch up to the time the element was created in the parent.

@lidlanca
Copy link
Contributor

same story for elements moved to an iframe.

@yyx990803 yyx990803 added 🐞 bug Something isn't working 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Jun 22, 2021
@yyx990803 yyx990803 added 🔩 p2-edge-case and removed 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Jul 21, 2021
@AurelieV
Copy link

AurelieV commented Oct 8, 2021

For information, this is how we temporaly fix it for our usecase (App which load components inside iframe)

iframe.onload = () => {
performance.now = iframe.contentWindow.performance.now
}

Tested only on Chrome, this is a way to resynchronise the timestamps used by Vue

@danielSt-dev
Copy link
Author

danielSt-dev commented Oct 13, 2021

@AurelieV thanks for your workaround! It works great!
The only thing I had to do is wrapping the assignment in a new function:
performance.now = () => { return this.windowRef.performance.now(); };
Otherwise the performance.now will not be overridden.

@fnlearner
Copy link

when I use teleport whose to is shadow root, there is also a relatively long time until the click handler will be registered (about 5 seconds)

@semiaddict
Copy link

This seems related to #2513.

@danielSt-dev
Copy link
Author

danielSt-dev commented Jun 14, 2022

Hi,
we updated our app to vue 3.2.37 and the workaround didn't work anymore.

It is because of merge #5944 (vue version 3.2.36). the call to performance.now through a function is replaced by a direct function assignment, so we can not override the performance.now anymore.

Current solution is downgrade to vue 3.2.35

@lidlanca
Copy link
Contributor

lidlanca commented Jun 16, 2022

another workaround

    <script>
      // Skip timestamp check workaround.
      // Code must be called before vue runtime is imported/evaluated.
      
     /*
       const ffMatch = navigator.userAgent.match(/firefox\/(\d+)/i);
       skipTimestampCheck2 = !!(ffMatch && Number(ffMatch[1]) <= 53);
     */

      let ua = window.navigator.userAgent;
      Object.defineProperty(window.navigator, 'userAgent', {
        get: () => {
          let s = new String(ua); 
          s.match = function (re) {
            // only care about this particualr match call.
            if (re.toString() === '/firefox\\/(\\d+)/i') {
              return [, 53];
            }
            return ua.match(re);
          };
          return s;
        },
      });
      // if we don't mind mutating userAgent, shorter version
      // Object.defineProperty(window.navigator,'userAgent',{get:()=>'firefox/53'})
    </script>

https://stackblitz.com/edit/vitejs-vite-gwxddy

related issue: #2513

@aprovent
Copy link

I encounter the same bug in Chrome.
I have a main VueJS app that load, one by one, multiple apps in an iframe.

All works perfectly, except the events in iframe apps. At the first load, the events are fired with a little delay, and as navigation go, the delay increase and the apps become unusable.

Thanks @AurelieV , I have begun to understand with your comment where the problem originated.

Unfortunately your solution was not enough for me :

iframe.onload = () => { performance.now = iframe.contentWindow.performance.now }

After several hours of research, i found that the event.timeStamp of each events was the key. It must be relative to performance.now(), that was not the case for me, even if i replace the main performance.now by the one in the iframe.

Here the solution thats works for me :

iframe.onload = () => {
   iframe.contentWindow.performance.now = () => performance.now();
   iframe.contentWindow.addEventListener('click', function(e) {
   	Object.defineProperty(e, "timeStamp", { 
   		get: () => performance.now(); 
   	})
   }, true);
}

This will make the click event works as soon as the application is mounted.

For all events, a little loop over onXXX properties could do the trick.

Here the final solution (all events type) :

const handler = (e) => {
    Object.defineProperty(e, "timeStamp", { get: () => performance.now() })
}
const events = Object.keys(window).filter(name => name.substring(0, 2) == 'on').map(name => name.substring(2));
events.forEach((name) => iframe.contentWindow.addEventListener(name, handler, true));

Hope it will help someone ;-)

chrislone pushed a commit to chrislone/core that referenced this issue Feb 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 bug Something isn't working 🔩 p2-edge-case
Projects
None yet
Development

No branches or pull requests

7 participants