Seamless iframes

The future, today!

Best viewed in Chrome version 22 or better (i.e. Chrome Canary)

Who invited this guy

  • Ben Vinegar
  • Totally legit lastname
  • Front-end Engineer at Disqus
  • Co-author, Third-party JavaScript
  • @bentlegen

DISQUS

  • Distributed commenting platform
  • Third-party JavaScript application
  • Bloomberg, IGN, Time.com, MLB ... more
  • 500 quadrillion pageviews per day

Re-written for 2012

  • Backbone.js + Ender
  • LESS
  • Uses the Disqus public API (mostly)
  • New: realtime streaming
  • Completely iframed

I know what you're thinking

  • Dear god, not another 3D slide deck
  • Iframes, srsly?

Iframes are great

  • They're sandboxes
  • Avoid JavaScript conflicts
    Array.prototype.push = '(╯°□°)╯︵ ┻━┻';
  • More secure
  • Better privacy for users

Iframes are bad

  • Don't inherit parent CSS rules
    iframe body { font-family: "Comic Sans"; } /* LOL */
  • Hyperlinks navigate iframe, not parent
  • Don't expand to fit contents

Iframes are bad

seamless attribute

<iframe seamless src="http://parent.com"></iframe>

seamless attribute

Your browser doesn't support seamless iframes.
This slide requires Chrome 22 or better (Chrome Canary).

Fantastic.

The cold, hard truth

  • seamless is not implemented in any stable browser
  • Chrome 21.x and up (beta or canary)
  • http:// only – not file://
  • Other browsers have superficial implementation:
    iframe[seamless]{
        background-color: transparent;
        border: 0px none transparent;
        padding: 0px;
        overflow: hidden;
    }

Other browsers?

  • You're boned.
  • Nah, just kidding. We can emulate seamless
    using JavaScript and esoteric HTML.

The plan

  • Two scripts:
    • JavaScript on the parent page
    • JavaScript inside the iframed document
  • Communication: window.postMessage

postMessage primer

// Parent
var iframe = document.getElementById('the-iframe');

iframe.contentWindow.postMessage('sup', '*');
// Iframe
window.parent.addEventListener('message', function (e) {
    var data = e.data;

    alert(data); // sup
}, false);

Relative
links

Recall ….

Relative links

  • Easy — use the target attribute
    <a href="http://example.com" target="_parent">!<a>
  • Must be repeated for every link
  • Gets tired fast, error prone

<base>

  • Specifies base URL for all relative URLs
  • HTML 4.01
  • Goes in <head>
  • Supports target attribute (_blank, _parent, etc.)

<base>

<!DOCTYPE html> <!-- the iframe -->
<html>
  <head>
    <base href="http://parent.com/" target="_parent">
  </head>
  
  <body>
    <a href="#boom"/>
    <!-- becomes: http://parent.com/#boom -->

    <a href="http://disqus.com"/>
    <!-- unchanged, still opens in parent -->
  </body>
</html>

Referring URL

  • How do you obtain it?
  • document.referrer – not 100%
  • Better: window.location + postMessage

Referring URL

// Parent
var iframe = document.getElementById('the-iframe');

iframe.contentWindow.postMessage(
    ['referrer', window.location.href], '*'
);
// Iframe
window.parent.addEventListener('message', function (e) {
    var eventName = e.data[0],
    var data      = e.data[1];

    switch (eventName) {
        case 'referrer': injectBaseElement(data); break;
        ...
    }

}, false);

Inheriting
styles

Inheriting styles

  • Many strategies …
  • Walk CSS tree and figure it out (read: no)
  • Inject predefined CSS into iframe
  • Poor man's choice: selective style detection using iframe parent element ✓

Detecting styles

  • Inject test elements as siblings of iframe element
    <div>
      <a class="test"></a>
      <span class="test"></span>
    
      <iframe src="yerpage.html"></iframe>
    </div>
  • Determine their style properties (font, colour, etc.)
  • Pass into the iframe

Detecting styles

// Parent
function getIframeStyles(iframe) {
    
    var $span = $('<span/>')
        .appendTo(iframe.parentNode);

    var styles = {
        color:      $span.css('color'),
        fontFamily: $span.css('font-family'),
        fontSize:   $span.css('font-size')

        ...
    };

    $span.remove(); // Cleanup

    return styles;
}

Style injection

// Parent
var iframe = document.getElementById('the-iframe');

iframe.contentWindow.postMessage(
    ['styles', styles], '*'
);
// Iframe
window.parent.addEventListener('message', function (e) {
    var eventName = e.data[0];
    var data      = e.data[1];

    switch (eventName) {
        case 'styles':   injectStyles(data);         break;
        case 'referrer': injectBaseElement(data); break;
        ...
    }
}, false);

Style injection

// Iframe
function injectStyles(styles) {
    $(document.body).css(styles);
}

Dynamic
content

Dynamic resizing

  • Inside iframe, determine content height
  • Use postMessage to send value to the parent
  • Parent sets height on iframe element
  • If the DOM changes – start over

Fit to content

// Iframe
function resize() {
    var height = $(html).height();

    // Backwards – send message to parent
    window.parent.postMessage(['setHeight', height], '*');
}
// Parent
window.addEventListener('message', function (e) {
    var $iframe = $('#the-iframe');

    var eventName = e.data[0];
    var data      = e.data[1];

    switch (eventName) {
        case 'setHeight':
            $iframe.height(data);
            break;
        ...
    }
}, false);

Detecting DOM changes

  • Mutation events – deprecated (and slow anyways)
  • Mutation Observers – DOM Level 4 (no time soon)
  • setTimeout - check size on interval (slow)
  • If you want something done right …

DIY DOM change signals

  • Whenever you modify the DOM, resize the frame
  • Problem: querying content dimensions forces browser reflow
  • Translation: every DOM modification will cause a reflow

Debounce

updateDom(function () {
    $('#foo').append('<div>bar</div>');
});

updateDom(function () {
    $('#baz').addClass('bigger');
});
function updateDom(callback) {
    callback();
    resize();
}

// Fires resize function 50ms after *last* call

var resize = _.debounce(function () {
    _resize();
}, 50);

Debounce

  • One reflow per set of uninterrupted DOM changes
  • Debounce duration (50 ms) isn't noticeable
  • Error prone – you can forget to use it
  • "It tastes awful, but it works"

The result

  • (Switch to hastily written demo)

Thanks.