Loading JavaScript modules via IFRAME

Another in the list of "I don't have a need for it now, but..." - this is also old hat on the Web but was new to me.

I've been intrigued by the Modules proposal for ECMAScript "Harmony". Since I like polyfills (so soothing...) I've been wondering what the subset is that could be implemented as a polyfill is, so that you can write code that runs in both ES5 and ES6 environments (i.e. the Web). I noodled around a bit with this many months ago and the key piece that was missing is sandboxed evaluation of the code, with unique "intrinsics" i.e. Object, Array, their prototypes, etc.

The scenario is that you're running code like:

Object.prototype.awesome = function() { return "foo"; };

But you load a third party module that one day decides to add:

Object.prototype.awesome = function() { return "bar"; };

If partying on the Object prototype isn't part of the contract with the module (and it shouldn't be!) then this breaks your code. Bummer.

Enter <IFRAME>! Each web page (or frame) gets its own global environment. And if the parent and child frame are in the same domain (including dynamically created child frames) then they can share JS objects even though they have different intrinsics. For example:

function iframeEval(program) {
  var iframe = document.createElement("iframe");
  document.documentElement.appendChild(iframe);
  var script = iframe.contentDocument.createElement('script');
  script.appendChild(
    iframe.contentDocument.createTextNode(
      "window.result = (" + program + ")"));
  iframe.contentDocument.documentElement.appendChild(script);  
  var result = iframe.contentWindow.result;
  iframe.parentElement.removeChild(iframe);
  return result;
}

console.log(iframeEval("1+2"); // 3
console.log(iframeEval("Object") === Object); // false (yay!)

Note that this does not defend against malicious scripts - they can grovel their way back out of the iframe and affect the parent page because they are running in the same origin. However, it does defend against unintentional pollution of the intrinsics, while not restricting a module's use.

This can be extended from an "eval" to a "load" concept. Instead of setting the program source as text content of the script tag, its src attribute can be set to load the module code from an arbitrary URL. (No cross-origin restrictions either, unlike loading the code with XHR.) In that case, the script loads asynchronously - but that's generally a good thing, although detecting when the load is complete so that a callback can be triggered is tricky (cross browser, and important note about IE9). There's also the question of what to pass back to the callback - you could simply pass the iframe's contentWindow (i.e. "this" or "self"). That approach requires that the caller knows what to dig out, which is reasonable.

Comments