ponyfoo.com

How To Avoid Object.prototype Pollution

Fix

Some times you just need to extend Array objects. Think of the possibilities. Dream of how much more awesome jQuery would’ve been if it provided all of the Array methods in its god-object, instead of re-inventing the wheel. Think how awesome it’d be if the DOM itself provided Array objects everywhere, and not just in some places, and not weird NodeList objects.

It’d also be pretty great if you could extend Arrays by yourself in a meaningful way. We all know how preposterous it is to extend the almighty Array object. There’s whole slew of issues that can arise from doing that, and that’s the reason why most of the community has shied away from polluting the global namespace, and more importantly, from polluting the Object, Array, and Function prototypes.

There is a way to have it both ways. You can extend Array all you want, and you can also not touch the Array object. Curious?

All you need is a bag of tricks, a magic wand, and another execution context. Or just ignore my blog post and run to the source, Luke.

Really it’s not as complicated as it sounds. In Node, it involves just 5 lines of code. The vm module allows you to run code in a new execution context, meaning you get a brand new Array.prototype. Turns out, it’s quite simple to grab a reference to any of that context’s globals, and run with it. In this case, I’ll be stealing the Array global.

var vm = require('vm');

function poser () {
  var sandbox = {};
  vm.runInNewContext('stolen=Array;', sandbox, 'poser.vm');
  return sandbox.stolen;
}

Now every time I run poser() I’ll get a brand new Array that I can extend at will, without affecting the Array everyone else uses. Modularity much? Of course, this isn’t just limited to Array. You could do this with Object, Function, or just about anything that is accessible from the global namespace.

You may be thinking, “well, the browser doesn’t have no stinking vm module!”. It’s a bit trickier, but it can be done too.

In the browser, the <iframe> tag is the go-to sandbox-provider. It provides an accessible execution context, and we can use that to steal their coveted prototypes away. Running a tiny bit of global-stealing JavaScript will do just fine. I’m only hiding the <iframe> rather than removing it, because I suspect removing it would make older browsers think it’s okay to garbage collect the execution context for that <iframe>.

var frames = global.frames;
var key = 'stolen, but no problem, just a poser';

function poser () {
  var iframe = document.createElement('iframe');
  var altdom;
  var stolen;

  iframe.style.display = 'none';
  document.body.appendChild(iframe);

  altdom = frames[frames.length - 1].document;
  altdom.write('<script>parent["' + key + '"]=Array;<\/script>');

  stolen = global[key];
  delete global[key]; // pollution-free environment!

  return stolen;
}

You’ll have to momentarily pollute the global namespace, so that the parent frame can access the out-of-context variable, but you can safely vanquish the global afterwards. Using an inspired global variable name, or checking that the property didn’t exist before, is enough. The only issue that I can think of is security mad men doing things like Object.freeze(window).

I’m curious about the performance implications in doing this, any comments on that will be thoroughly appreciated!

I, for one, will definitely be using poser, and I’ll make sure to comment on whether it’s reasonable to go down that road.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (4)

TheSisb wrote

I can’t think of a situation where you would consciously intend to modify the array prototype while knowing of the dangers. If you have a conscious thought around that decision, you can usually come up with some alternative solution.

Creating an iframe is really slow, I spent 3 minutes creating an extremely basic perf (http://jsperf.com/poser-perf-test-attempt) to determine how slow that process is and the results were unsurprising.

This perf test isn’t by any means all encompassing, but to me is enough to reconsider this approach, at least from the browser’s perspective.

Interesting technique though!

Nicolas Bevacqua wrote

Your test isn’t fair at all to poser, because it’s creating an iframe on every iteration. Here’s a more reasonable performance test that doesn’t create an iframe on every iteration.

posertest.png
posertest.png

As you can see: yes, poser is a bit slower, but not significantly.

TheSisb wrote

I wasn’t really trying to make poser look bad or anything - I was just trying to figure out how slow iframe creation was in general. So poser isn’t that much slower from that point of view, but you still have to create the iframe to use it which isn’t considered in your test. :P

Again, a very interesting idea. I just don’t think that’s the right solution to the best practice of not extending Arrays prototype and such. Could you show me where you use it?

Nicolas Bevacqua wrote

I use it in Dominus, a jQuery-like library, to allow for things like $('foo').forEach(fn).