Random Rabbitholes about IFRAMES and WP’s Script Loader

My script was working, but it wasn’t loading correctly the first time. I had to reload to make it run. Unacceptable.

I spent a full day going down a lot of interesting rabbitholes, but ended up finding a really simple, almost obvious, solution. That’s a good thing, but the torturous journey is the reward.

Polling for Objects to Appear

The problem was a common race condition: window.top.wp.Backbone didn’t exist when I needed it to.

So, I went with the typical solution, which was to use window.setInterval() to poll the object to see if it exists, and when it does, run the init() function and start the application.

It didn’t work. I’m not even sure why, but it appeared that if I used setInterval (or setTimeout) within an IFRAME, it prevented the async JS files from loading in the parent document. I’m not certain of this – but that’s how it looked.

I thought maybe the browser didn’t recognize that the scripts had loaded. I looked at the headers: could it be Keep-Alive was keeping the socket open so long that was not indicating an end of the script?

This mattered because WP was using a script called load-scripts.php to concatenate and compress all the Javascript files.

Some study turned up a header called “Transfer-Length” that corresponded to the length of the compressed data. I set it – and it made no difference.

So setInterval polling was out.

Script Dependencies

Then, I messed around with moving the dependencies higher up, into the HTML HEAD. To get the stuff up in there, you need to modify the default script loading dependencies that are defined in WP. WP keeps a registry of all the scripts, and indicates what each script depends on, and whether the script can load at the bottom of the page.

It turns out most of the scripts get loaded down there, but using $wp_scripts->remove() and add() I could move scripts upward into the HEAD.

It kind of worked. More of the application initialized correctly, but the code in the handlers referred to things that didn’t exist when the handlers were created!

I pulled more dependencies upward, and more code ran, but I’d hit some error.

There had to be a better way.

Calling Code from the Enclosing IFRAME

The fix that worked was to call the initialization code from the enclosing document.

jQuery(window).on("load", function() {

jQuery(window).on(“load”, …) fires only after everything on the page has loaded. This includes the content in the IFRAME. So, we know that the enclosed document will be completely loaded when we call fe_init_from_customimzer(). This is exactly what we wanted.

The one liner inside shows how to call code within an IFRAME, from the enclosing document.

This solution worked great.