Authors Archive

 
 

Resize a Crossdomain iFrame

When it comes to crossdomain quirks with frames and Ajax, there’s not usually a “good” solution — there’s just one that works. Something I was working on the other week had a “well it works” moment, although the solution was far from ideal.

The Problem

The page in question is the Sponsored These Tweeters page at SponsoredTweets. It’s a pretty basic Wordpress page with an iFrame that contains the list of Tweeters. Real easy way of adding a dynamic touch to a Wordpress site. The issue is that the iframe contains a dynamic amount of content, and could potentially grow or shrink based on the length of amount of tweeters being shown. The kicker? The page with the list of tweeters is at a subdomain, and under https.

Monitoring with Jquery

My first idea with anything like this is “Can I just listen for a jquery event on the element?”. Well, sure, but the height of the iframe won’t change. You’ll only be able to get the height/width of the iframe itself, not the content within it from the page itself, so watching for a resize or load event on the iframe won’t work. What you’re really wanting to listen for is a change in the height of the content

Works? No.

Reaching into the frame with contentWindow

If your frame and the page containing it both exist on the same domain, you can reach into DOM of the frame and get the height that way. At anytime you can do something like this to get the height of the page:

document.getElementById("iframeid")
         .contentWindow.document.body.scrollHeight

This bit of code will return an integer — the height of the content of the frame in pixels. Even if the iframe has a height of (for example) 300px, the content itself could be smaller or larger. Unfortunately you can’t set a jQuery event watcher on document.getElementById("iframeid").contentWindow.document.body either (at least from what I’ve seen), so I can’t see a way to watch for a change in height.

The “not-so-nice” fix for this is to make a really fast function that repeatedly checks a change in the height, and resizes the iframe accordingly. Here’s some sample code for this using jQuery.

var $iframe = $("#iframeid");
function resize_iframe() {
  var current_height = $iframe.css("height");
  if(current_height != $iframe[0].contentWindow.document.body.scrollHeight) {
    $iframe.css("height", $iframe[0].contentWindow.document.body.scrollHeight);
  }
}
setInterval("resize_iframe();", 100);

Everytime I use setTimeout() or setInterval() in Javascript, it’s immediately a red flag. These (along with the evil-eval) can be an obvious code smell, but also a trigger that the code itself isn’t designed in the best way.

The problem with this though is that it won’t quite be realtime. Also the contentWindow.document object is not available cross-domain. Even doing things like contentWindow.document.location.href to get the current location of the frame doesn’t work if the calling page and the frame are on different domains/subdomains. This might work for some cases, but not if your iframe is on a different domain.

Works? Yes.
Works on the same domain? Yes.
Works on different subdomains on the same domain? No.

The document.domain hack

If both your frame and your calling page exist on the same parent domain, there’s a little hack you can do to get this to work. Each page has a document.domain variable that contains the domain from which ajax calls can be made to. If you rails application is up at https://app.sponsoredtweets.com, then your document domain will be set to app.sponsoredtweets.com. Of course your main website sitting at http://sponsoredtweets.com will have a document.domain ot sponsoredtweets.com. Because of this they won’t be able to talk to each other. What you can do is manually change the document.domain value.

Surprisingly enough, you can tweak this value as long as you only get broader in your domain. For instance, if document.domain is app.sponsoredtweets.com, then you’ll be able to manually set this using something like this: document.domain = "sponsoredtweets.com" From that point on, that page will be treated as those requests were coming/going to that domain. Using this, you could set your iframe and the page containing the iframe to be on the same root domain, then reach into it’s contentHeight as described above. After you’ve set this to “sponsoredtweets.com” though, you won’t be able to change it again, as that’s the most general a document.domain can get.

Works on the same domain? Yep.
Works on different subdomains on the same domain? Yes.
Works crossdomain? No.
Works across https? No.

Frame within a Frame within a Frame

Unfortunately, none of this works across domain, of from http connections to https. Here’s how it works for SponsoredTweets:

The main page is http://sponsoredtweets.com/tweeters/sponsor-these-tweeters/, aka, the parent page.
Which has a frame to https://app.sponsoredtweets.com/tweeters, aka, the content page.
Which has a frame to http://sponsoredtweets.com/iframe.html, aka, the placeholder page.

The middle frame is the one that will change in height of course. For the same reasons as above, the middle frame cannot reach into it’s parent frame and call methods there either. It can, however, control it’s own iframe in a very limited sense. For instance, take this line from the

$("#parent_domain")[0].contentWindow.location = 'http://sponsoredtweets.com/iframe.html#' + height;

Whenever the height of the content page changes, it updates the location of it’s frame to include a hash tag followed by the current height of the content. By itself this won’t do anything, but if that iframe.html page is watching for changes to it’s location, we can start from there.

The placeholder page is what makes this all possible. All it has to do is monitor for changes to it’s hash, and when it sees them, send them up to the parent page (that is, the top level page). Now, although the content page can’t talk to it’s parent page, the placeholder page can talk to it since they are both on the same domain. Is it a hack? Oh yeah, and of the worst kind.

<html>
<script type="text/javascript">
<!--
  function sendHash() {
    var location = window.location.href.split("#")
    if(location.length != 2) { return; }
    parent.parent.receiveHash(location[1]);
  }
  setInterval("sendHash();", 100);
-->
</script>
<body></body>
</html>

parent.parent…? Yeah, thanks HTML. All the receiveHash() function on the parent page does is take a number and set it as the height of it’s iframe. Real simple implementation, it’s just the craziness that is cross-domain security that makes it difficult to grasp.

Works across https? Yes!

Got a Better Solution?

I can’t believe this would be the ideal way to do something as simple as resize an iframe. Do you know of a better way of doing this given that the pages exist on separate domains? I’d love to hear about it.

Setup RSS Feeds using FeedBurner

I’m sure everyone has experienced this: you’re reading their feeds and all of the sudden one of them has 20 new entries. Did the author of that feed suddenly go wild and start posting? Maybe they’re at a special event and posting about it? No, usually it’s just an error in the RSS feed where somehow the timestamps of the entries are different, causing your feedreader of choice to show you 19 articles that you’ve already read. This used to be very annoying, but now a days it happens so often I barely notice it — just click over to that feed, click ‘mark all as read’, and go on to the next feed.

One of the easiest ways to get around this is not to rely on your website to host your RSS feed. Setting up your blog to use FeedBurner is so trivially easy, that it should be one the first time for you to do with a new Wordpress installation. Really simple steps to do it to. You ready?

Install the Wordpress Plugin

Install the FeedSmith plugin. Directions and plugin link on the Google support page, so it’s pretty straightforward. Just upload the plugin and activate it from the plugins panel.

Update your .htaccess file

Update your .htaccess file to rewrite your /feed link. This will allow people to actually add your local feed link to feedreaders, then if the destination changes it won’t affect your users. For instance, if someone adds http://adamfortuna.com/feed to their feedreader, right now it’ll just redirect to FeedBurner. Later on this might go somewhere else, but I can do that without worrying about losing my (tiny) readership.

Very easy to do – and worth setting up if you plan on having your blog around for a while. Also it greatly reduces your server load not having to rely on your feed being checked every minute by loads of feed readers.

Snow Leopard Upgrade Not Always Smooth

After reading post after post of smooth upgrades from Leopard to Snow Leopard, I felt more obliged to share my not-so-successful upgrade story. You must remember, if you’ve been using your computer a while, there’s a much greater chance that there’s a problem with it, and that installing a very large enhancement on top of that possibly fragile groundwork might cause issues. Make sure you have some backups (you use Dropbox right?), and that you do a full system save using TimeMachine prior to starting.

What Actually Happened

Insert disc, go through install. Apple products have easy installs right? No doubt there – within a minute it was already copying files over and doing it’s thing. Unfortunately it hit a very odd error “Unable to complete upgrade” with an option to restart. OK, time to restart. Booting up was when the problem hit me though. I saw the normal gray loading screen with the spinner, but it went from that to a complete black screen and turn off. Not a good start. After trying this a few times, I eventually tried holding option on boot, which brings up a boot menu allowing you to select which device to boot from.

With the Snow Leopard DVD still in the drive, I choose to boot from it. It made it to the install window again, and again I gave installing a shot (although much more pessimistic this time). Again it failed, but it suggested I run Disk Utility to verify my drives. Ok, good advice. Luckily you can run these straight from the installer (there’s the default menu bar up top with a “utilities” menu with “Disk Utilities” as one of these. This is the same program you should have available through Leopard. I select my drive, click verify and within a few minutes I see a big red flashing error message that says my drive is corrupt and that I should back everything up using TimeMachine, format and recover an install from TimeMachine. That’s when I realized my first mistake – not verifying the health of my drives before upgrading my OS.

Rather than reinstalling Leopard and upgrading to Snow Leopard, I opted for a complete hard drive format, then installed Leopard on that. It didn’t seem to have a problem installing everything, which was quite a relief. When you think ‘upgrade’ you don’t think you’d be able to get doing from a formatted disk, but luckily Snow Leopard didn’t have a problem with this. Eventually I had a completely clean install of Snow Leopard on a newly formatted drive.

At this time if you have a clean TimeMachine backup you can use it as a starting point to import all your files/settings/passwords/etc. I decided to start from scratch (perhaps it’s the lingering Windows masochist in me), and install only what I need. In not too long I had plenty installed, and didn’t miss what I didn’t know I was missing. The nicest part though, was that with TimeMachine you can just jump in and grab an old file from any computers backup. As someone who hasn’t used TimeMachine very much over the years, it was welcomed relief to backup. Still though, I prefer Dropbox for it’s ability to backup + sync files across multiple computers — but for large files it’s hard to beat TimeMachine.

What I Should’ve Done

Run Disk Utility first to verify disks, and repair them if you can. At least at that point you’ll have one more piece of information on if your Snow Leopard install will be a complete failure or not.