Delayed registration of event listeners in a Vue 3 instance inside an iframe

Viewed 162

For a very specific reason*, I need a Vue instance to run inside an iframe. I managed to do that following this thread on Vue forum, and tweaking the code to support Vue 3 and removing the slot, because I only need a specific component to run inside an iframe.

But there is a very specific problem that I haven't even managed to start debugging.

As you can see from this minimal reproducible example on CodeSandbox, when a new element is added into the iframe, it takes a considerable amount of time for the user events (namely clicks) to start getting recognised. (it is even more pronounced in my scenario, to the point of being completely unusable)

Does anyone who is well-versed in Vue at least understand why this is happening and if there is a way to prevent it from happening?

Any insight would be much appreciated. Thanks.


*the reason being that I am building a browser extension that injects a sidebar into the page and I need to isolate the CSS, because the extension is to be used on various pages, so it is nearly impossible to prevent all stlyes from leaking into the injected sidebar.

Yes, avoiding an iframe and using #myDiv{ all: unset } instead works to some extent, but fails for some very specific selectors defined in the page's CSS (and outright does not work for input elements). Hunting for them on all pages that I want to support would quickly become a nightmare.

1 Answers

Answering my own question, since no one has shed any light on this. This does not, hovewer, answer the original question, but I figured that should anyone stumble upon this problem, they should know how I managed to solve it.

In the end, I ditched the idea of an iFrame and used the concept of Shadow DOM to isolate the page's CSS and the injected extension's CSS from each other.

Although this approach comes with its own caveats*, it is IMHO superior to using iframes.

*like the need to manually inject custom styles into the shadow-root. And also the need to store the shadow-root element somewhere that all parts of your application can access (NOT Vuex, the element itself cannot be stored there) in order to make things like work.

Here is a snippet of code I used to inject the sidebar into the page:

// append a new node to the document body
let shadowHost = document.createElement("div");
document.body.appendChild(shadowHost);
    
// make it the root of our shadow DOM
let shadow = shadowHost.attachShadow({ mode: 'open'})

// create a new node for the sidebar
let sidebar = document.createElement("div");
sidebar.id = "my-sidebar";

// and also a style node into which we put all our custom css
const style = document.createElement('style');
// getPackedCss is my function that returns all the custom css as a string
style.textContent = getPackedCss();

// and append those to the shadow DOM
shadow.appendChild(style);
shadow.appendChild(sidebar);
    
// store the shadow root somewhere that we can access
// globally in order to select elements from it
staticStore.shadowRoot = shadow;

// create our vue app and mount it to the shadow DOM
const app = createApp(MyApp);
app.mount(sidebar);

If you reeeally need Vue 3 and an iframe to like each other, I guess you are on your own here.

Related