Vue 3 does not work well with iframes
Version
3.0.2
Reproduction link
https://codesandbox.io/s/unruffled-northcutt-cmndl?file=/public/iframe.html
Steps to reproduce
- Have an app that shows an iframe
- Create a teleport that will place it's content inside the iframe
- Add an event like click to the teleport content
- 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
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.
@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 ( https://github.com/vuejs/vue/issues/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.
I have the same problem in 3.0.7. Is there a solution now?the reproduce
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.
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.🙁
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
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.
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 👍
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).
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>
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 )
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.
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
For anyone else attempting @zauan's fix in Vite, here's what I needed to do-
- Update /node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js as per his gist file
- Clear out all files from /node_modules/.vite
- npm run dev
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).
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.
related issue : https://github.com/vuejs/core/issues/3933
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?
@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 https://github.com/vuejs/core/issues/3933#issuecomment-1157259673
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.
@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.
@semiaddict @Kyon147 the source of this snippet is from here https://github.com/vuejs/core/issues/3933#issuecomment-1157259673
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.
@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
Has anybody come across this issue with Vue 3 using the Options API? Any idea if it's been resolved yet at all?
+1 @miainchambers
We're about to starting building a Vue application for a client but have been informed that it may need to be placed inside an iframe for 3rd party sites using the app; what problems do we need to be aware of with this in 2023?