Felocity

Bangs on Keyboard, Makes Shiny

I am Jakob Heuser. I code, I write, & I strive to keep things simple. Photography is a hobby I'm happy not being paid for. I work at LinkedIn, and enjoy making an impact.

inserBefore, appendChild, and Reloading Iframes

Read The Full Post

If you’re playing around with iframes, there’s a nasty bug that occurs when you attempt to move the iframe using methods such as appendChild() or insertBefore(). How nasty the bug is depends on how tolerant your code is of reloading behaviors. Any attempts to move the iframe around will cause reloads in older Safari as well as Firefox. The problem lies in how the browsers interpret these events, removing the node and then adding it to its new destination which triggers a reload. Dragging and dropping iframes comes to mind as the first of many scenarios that may have this problem, and developers using LinkedIn’s Platform or the Facebook Social Widgets are likely to notice this weirdness. Strangely enough, IE doesn’t exhibit this behavior, which leaves the problem to just Webkit and Firefox.

Workarounds in CSS

A quick scan in your search engine of choice reveals only one consistent technique for moving iframes about on the page- positioning absolutely. An absolutely positioned iframe can be readjusted easily, and can track directly on top of a placeholder such as a div. This works pretty fantastically in all but the most complex of layouts where absolute positioning requires a relative parent in just the right place.

  <div id="#placeholder"></div>

  <!-- ...last node in the page... -->
  <iframe id="theFrame" src="http://www.example.com"
   style="left: -12345px; top: 0; position: absolute;"></iframe>
</body>
(function() {
var div = document.getElementById("placeholder");
var iframe = document.getElementById("theFrame");

window.setInterval(function putFrameOntoPlaceholder() {
  // 1) measure the size of the iframe, change size of div
  div.width = iframe.offsetWidth + "px";
  div.height = iframe.offsetHeight + "px";

  // 2) get the div's position on the page, position iframe
  iframe.left = div.offsetLeft + "px";
  iframe.top = div.offsetHeight + "px";
}, 100);

})();

In layouts that make use of lots of relative positioning, it’s easy to see where this might run into snags. Styles on either the div or iframe could mess up the alignment a bit, and while it’s correctable it’s not very feasible for a large scale site where there’s going to be several developers building code.

Webkit and Gmail: Adopting Nodes, not Importing

In July 2010, Google silently released an improvement to Gmail. It was most noticeable when using the gmail chat feature. You could open the chat window, “pop” it out of the main window, and then close the parent window and everything continued to work. The most fascinating part about this was the minimal load time in the popup. It was as if the entire Gmail environment were being ported over to the popup as part of the unload events. That’s exactly what’s happening. The iframe contained the code both the main page and popup needed. Move it to the new DOM, and presto, no reload! The reloading of an iframe in Google’s case wasn’t horrible, but at a company that obsesses over milliseconds, having to reload an entire JS stack was brutal. The initial webkit bug on reparenting iframes would ultimately result in the ideal fix.

Somewhat quietly, DOM Level 3 core had added an operation called adoptNode(). Adopting an HTML node would simply move the node and in the case of the iframe, would not trigger a reload of the content. Coming back to Gmail, the JavaScript needed for the popup window is loaded into an iframe. When the parent window is closed, adoptNode() is called, moving all of the parent’s necessary code into the popup window before it closes.

And Firefox? Opera? Insert-other-browser?

Unfortunately, as of this writing, Firefox still has an open ticket on the broken adoptNode behavior, with no fixes in sight. As a last ditch effort, we can fall back to importNode(), we will just take the performance hit and trigger an undesirable reload which can possibly erase state within the iframe if there is any.

This stuff hasn’t been validated against Opera. My hope is Opera’s implementation of adoptNode is more in line with Webkit’s, which leaves only Mozilla’s long standing bug.

Time For Defensive Coding Then

Using the above Firefox-proof code, it’s possible to use adoptNode() when it’s available, and then fall back to the importNode as a solution.

try {
  document.adoptNode(window.opener.$("#my-iframe"));
}
catch(exc) {
  // performance hit. Either importNode, or load the
  // frame contents with appendChild()
  document.importNode(window.opener.$("#my-iframe"));
}

The most significant difference is knowing you cannot maintain stateful items in the frame itself. Solutions for using adoptNode with a fallback will need some way to save and restore the state, possibly in localStorage.

Over on GitHub

See All My Projects

Inject 0.4.1 Around the Corner

Read The Full Post

It’s been a while since I’ve talked about some of the software coming out of LinkedIn. The open source projects we do continue to thrive. Of specific note is Inject which is in the process of maturing into 0.4.1. Now that the first RC is available, it’s a good time to talk about the framework and what’s changed. The last time I talked about Inject was on the LinkedIn Engineering Blog when we were at 0.3.0, so it has been a while indeed.

When We Became 0.4.0

Since the original launch, we made a few significant (backwards incompatible) changes that forced us to increment the second “dot” in our build system. Inject builds are best described as

milestone.not-backwards-ok.backwards-ok

That is, we believe it should be easy to glance at the version release and see if it’s going to break your existing code. If we’re changing that second value, we’re pretty sure things will break. And so with that, came a Migration Guide for Inject to ensure people could upgrade to the latest version. We stopped overloading the require() method, and started focusing on getting the public APIs under control.

Introducing 0.4.1

Since the 0.4.0 release, we’ve been adding a couple of key features. They should make developing with Inject even easier.

Plugins enable a developer to bring in non-JavaScript dependencies into Inject. We’re releasing 0.4.1 with a CSS, Text, and JSON plugin to illustrate the power. Requiring a CSS file for example is as simple as

var css = require('css!path/to/css_file.css');
css.attach();

The above is made possible because of the afterFetch pointcut. Unlike the original “before” and “after” pointcuts, the afterFetch pointcut allows for the asynchronous modification of the file after its download. This allows Inject to transform non-JavaScript code into safe strings for evaluation in the dependency system. As an added bonus, localStorage caches, cross domain functionality, and all the things that make Inject great continue to work.

The New Test Compliance means that we are no longer maintaining our own copy of the specification tests for AMD and CommonJS. The AMD Test Suite and CommonJS Suite are brought in as submodules, and some QUnit wiring holds it all together. This means we can validate our code against the specifications throughout development, and TravisCI can keep things running smoothly.

Finally, our older regexes are retired in favor of Link.JS. Link.JS is a module loader that takes advantage of the Named Modules format in a future ECMAScript. they approached Inject offering their AST parser, which works fantastically well. It reduces complexity of our main code base, takes care of a few (annoying) regular expression bugs, and all around makes the dev team happy.

Putting it all Somewhere

Last, with GitHub’s decision to remove the downloads tab, we needed somewhere to put all our releases. Consider this the “soft launch” of the InjectJS website at http://www.injectjs.com/. It’s full of documentation, full of howto guides, and is in general does the framework more justice than a simple source page could have done.

We’re up to RC2 on the download page, with RC3 following at the end of this week. If all goes well, 0.4.1 should hit developers hands here in early January. And now we have somewhere to officially announce it.

Photography

Contact Jakob