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

Vue 3 does not work well with iframes #2513

Closed
zauan opened this issue Oct 28, 2020 · 27 comments
Closed

Vue 3 does not work well with iframes #2513

zauan opened this issue Oct 28, 2020 · 27 comments
Labels
p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. feature request New feature or request

Comments

@zauan
Copy link

zauan commented Oct 28, 2020

Version

3.0.2

Reproduction link

https://codesandbox.io/s/unruffled-northcutt-cmndl?file=/public/iframe.html

Steps to reproduce

  1. Have an app that shows an iframe
  2. Create a teleport that will place it's content inside the iframe
  3. Add an event like click to the teleport content
  4. Immediately after the irame loads, try to click on the teleported element

What is expected?

The click event should fire immediatelly

What is actually happening?

The click is not working until the event.timestamp ( that is generated by the iframe dom ) gets higher than the attached timestamp from the parent window.


More info can be found here ( https://github.com/vuejs/vue-next/blob/ea5f92ae051be511558569eaf4c2afa2839c3e4d/packages/runtime-dom/src/modules/events.ts#L114 here, inside the iframe the event.timeStamp which is calculated based on the iframe timestamp is lower than the initial invoker.attached = getNow() that is calculated based on the parent frame ). Due to this, events are not firing for the time difference it takes the iframe to load

@martinuhlir
Copy link

martinuhlir commented Nov 21, 2020

I'm in a similar boat, not sure how much it's related to OP but it sounds very similar. I've a Vue.js app with an iframe, which loads a different Vue.js app. Sometimes, click events weren't registering in the nested app, commenting out the line highlighted in OP helped. It's weird because it didn't happen every time and both machines are running the same version of browser.

@zauan
Copy link
Author

zauan commented Dec 2, 2020

From what you experience, this is the same issue

@zauan
Copy link
Author

zauan commented Dec 2, 2020

@yyx990803 Looking at the source code, I noticed this comment accompanied the "fix" that introduced the problem above:

        // async edge case #6566: inner click event triggers patch, event handler
        // attached to outer element during patch, and triggered again. This
        // happens because browsers fire microtask ticks between event propagation.
        // the solution is simple: we save the timestamp when a handler is attached,
        // and the handler would only fire if the event passed to it was fired
        // AFTER it was attached.

I have made some tests using the reproduction template provided in the initial ticket ( vuejs/vue#6566 ) with Vue 3 and it seems that the problem mentioned there is not reproducing anymore if I comment out the timestamp check.

Are there other issues that involved that fix? If there aren't, I would like to make a pull request removing the timestamp check.

@niqingyang
Copy link

niqingyang commented Mar 23, 2021

I have the same problem in 3.0.7. Is there a solution now?the reproduce

@Kingbultsea
Copy link
Contributor

I have the same problem in 3.0.7. Is there a solution now?the reproduce

raise by the iframe's event.timeStamp. need to wait until the iframe's creation time longer than clickEvent's creation time.

@niqingyang
Copy link

I have the same problem in 3.0.7. Is there a solution now?the reproduce

raise by the iframe's event.timeStamp. need to wait until the iframe's creation time longer than clickEvent's creation time.

It's not a solution.🙁

@HcySunYang HcySunYang added the feature request New feature or request label Apr 16, 2021
@tungquach
Copy link

Is there any solution to fix this problem yet? Every time, I mounted an iframe without reloading the page, @click handler of element inside iframe won't work anymore

@LinusBorg
Copy link
Member

As indicated by the open issue, there's no solution yet.

@zauan
Copy link
Author

zauan commented Apr 30, 2021

The "solution" we are using in production is to manually modify a vue file and disable this ( runtime-dom.esm-bundler.js in our case ), however, I do not recommend doing so as other problems may appear.

The changes we've made:
https://gist.github.com/zauan/a3488a9feb799242df607c983de9c5b7

We have this modification in a live product ( zionbuilder.io ) and didn't encounter any side effects so far. I also tested the initial bug that introduced this modification and I cannot reproduce it in Vue 3 ( with the timestamp check disabled ), so, I am not sure if the initial problem is still valid in Vue 3.

@tungquach
Copy link

tungquach commented Apr 30, 2021

The "solution" we are using in production is to manually modify a vue file and disable this ( runtime-dom.esm-bundler.js in our case ), however, I do not recommend doing so as other problems may appear.

The changes we've made:
https://gist.github.com/zauan/a3488a9feb799242df607c983de9c5b7

We have this modification in a live product ( zionbuilder.io ) and didn't encounter any side effects so far. I also tested the initial bug that introduced this modification and I cannot reproduce it in Vue 3 ( with the timestamp check disabled ), so, I am not sure if the initial problem is still valid in Vue 3.

Thank you so much, it was working great, I will try with a new fork 👍

@LinusBorg
Copy link
Member

LinusBorg commented May 1, 2021

I also tested the initial bug that introduced this modification and I cannot reproduce it in Vue 3 ( with the timestamp check disabled ), so, I am not sure if the initial problem is still valid in Vue 3.

@zauan Here's a demo of the behaviour this check is meant to protect against, simulated with a custom directive (which leaves event handling to your vanilla JS adn thus doesn't contain this check unless added manually:

https://jsfiddle.net/Linusborg/tc1aksvx/

It really is an edge case so things could seem to be working fine for 99.9% of the time with your change in place.

But you could still be right theoretically that the problem doesn't persist with actual v-on events with your modification, haven't had the time to properly test this (optimally across all supported browsers).

@williarin
Copy link

williarin commented Jun 17, 2021

The workaround to this problem is to register all events in onMounted() using vanilla events. This will bypass the timestamp restriction.

<template>
    <div ref="mydiv"></div>
</template>
<script>
export default {
    setup() {
        const mydiv = ref(null);
        
        onMounted(() => {
            mydiv.value.addEventListener('click', handler);
        });

        onBeforeUnmount(() => {
            mydiv.value.removeEventListener('click', handler);
        });
        
        function handler(event) {
            // ...
        }
        
        return {
            mydiv,
        };
    }
};
</script>

@zauan
Copy link
Author

zauan commented Jun 17, 2021

Yes, this works on smaller projects, however, this cannot be implemented in larger projects like Zion Builder as it increase the code base and there are many places were events are used. Also, can you please also add the removeEventListener on the 'beforeUnmount' hook to your code so that the event gets removed when the component gets unmounted ( in case someone wants to use you workaround and just copies and pastes the code )

@williarin
Copy link

Sure, I edited the code.

@alexbeae
Copy link

This issue is a nightmare for big projects. Adding events manually doesn't work for elements with v-if conditions. I hope this bug will be fixed soon.

@AurelieV
Copy link

AurelieV commented Oct 7, 2021

I confirm that the problem is still here, is there any update on that?

@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

@zetokore
Copy link

For anyone else attempting @zauan's fix in Vite, here's what I needed to do-

  1. Update /node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js as per his gist file
  2. Clear out all files from /node_modules/.vite
  3. npm run dev

@semiaddict
Copy link

I am also encountering this issue when mounting a component into an empty iframe.
The first click events are ignored. It can last a few seconds before click events are finally emitted.

Since the time check fix concerns only rare edge cases and introduces bugs in other cases, it might be interesting to allow disabling this check, either via a global application config, or locally per component (and its children).

@semiaddict
Copy link

FYI, I am working on a solution to allow opting out of the timestamp check.
I hope to be able to submit a pull request very soon.

semiaddict added a commit to philharmoniedeparis/vuejs-core that referenced this issue Feb 23, 2022
@yyx990803 yyx990803 added the p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. label May 13, 2022
@lidlanca
Copy link
Contributor

related issue : #3933

@Hyperblaster
Copy link

Really struggling with this one. None of the workarounds above seem to work reliably and @semiaddict 's pull request has been sitting there unmerged for over 6 months.

Is there any updates?

@Kyon147
Copy link

Kyon147 commented Aug 20, 2022

@Hyperblaster this was the only thing that worked for me.

    /*
    * This fixes the current issue with event timestamps when running vue in a window or iframe.
    * */
    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;
        },
    });

I can't remember who posted it originally, but it was on another ticket on this repo.

Edit: Credit to @lidlanca for this solution from #3933 (comment)

@semiaddict
Copy link

I wouldn't recommend overriding the userAgent as this can have side effects within and outside of Vue.

I've personally been patching Vue 3 with the changes in my pull request using patch-package without issues.

I just hope someone in the Vue team will get the time to look into the pull request soon.

@semiaddict
Copy link

@LinusBorg,
@yyx990803,

Any chance of getting the pull request reviewed?
I'm totally open on working on a different solution if for any reason the one proposed in the pull request doesn't quite fit the Vue coding practices.
Thanks in advance.

@lidlanca
Copy link
Contributor

@semiaddict @Kyon147 the source of this snippet is from here
#3933 (comment)

and it doesn't override the user-agent, it actually overrides the match() call of the userAgent
to minimize impact, and target the check vue does.

@Hyperblaster
Copy link

@lidlanca thanks so much! This helped me solve my issue. Adding the script into the head (before vue is initialised) seemed to do the trick. I tried all kinds of things in onMounted hooks and nothing seemed to work.

Hopefully this can be merged and fixed in core soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. feature request New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.