I don’t communicate between frames often, but when I do, I use postMessage

For one of our current projects, we developed a Pinterest-style ‘share’ bookmarklet which, when clicked from your bookmarks toolbar, injects an iframe containing a share form on top of the page you’re currently on. We spent a good chunk of time really polishing it up and making for a good user experience. You can even log into the site through the frame using a special view, since sharing requires authentication. We also pre-populated the form with information from Facebook’s Open Graph if possible, to make sure users don’t have to enter unnecessary information. We excitedly delivered it to the clients, and while they loved it, they had one additional request—after sharing the page, make the frame disappear after a few seconds.

It seems simple: “self.close()” or some such thing. The problem is, for security reasons, as a framed page, you can’t just close yourself. You can’t even access any properties on your parent across different domains because it violates the same origin policy. So how does the parent (in this case, some other website) know when an article has successfully been shared if we have no way of telling it?

It turns out that while we didn’t used to, we do now, thanks to HTML 5. After several failed attempts at setting shared variables on the window object and trying to access the contents of the iframe, only to be blocked by angry red “Unsafe JavaScript attempt to access frame” messages in the console, I discovered postMessage. It gives you a secure channel to send a message to another window or frame across different domains.

For the problem at hand, that means setting up a listener on the parent and a messenger inside the frame. Here’s what I ended up with (shown in Coffeescript):

Listener:

# For cross-browser compatibility
eventMethod = if window.addEventListener then "addEventListener" else "attachEvent"
eventer = window[eventMethod]
messageEvent = if (eventMethod == "attachEvent") then "onmessage" else "message"
 
# Listen for messages
eventer(messageEvent, (event) =>
  return if event.origin != myDomain
  setTimeout(@closeFrame, 3000) if event.data == 'autoclose'
, false)

Messenger:

parent.postMessage('autoclose', page.domain)

The interesting thing about the messenger is that we need to specify the domain (including protocol, hostname, and port, if applicable) the listener lives on. If we’re sharing an article on cnn.com (which we’ve injected our frame into with the bookmarklet), we need to pass ‘http://www.cnn.com.’ It’s also a best practice to check the origin of the event to make sure you’re only executing the callback if the message comes from a trusted source.

Now all that’s left to do is celebrate. I think I’ll go with a homebrew over a Dos Equis, though.