Architecting a Sandbox

Ian Johnson
5 min readAug 23, 2015

--

Sandboxes are safe places to play, to build with the basics and gain an understanding of how the world works. I’m now working on my second project that has a sandbox as its central feature. The first was Tributary, a live-coding environment inspired by Bret Victor’s Inventing on Principle talk. The second is Building Bl.ocks, a live-coding environment designed to make participating in the d3.js community much more accessible.

building-blocks UI

In both of these projects, the sandbox is an iframe where we can safely run arbitrary user-written code. We want to allow users to write and play with code, but also save and share it with other users for further tinkering and feedback. It’s also very important that the sandbox updates as the user makes edits to their code or their data, giving them the immediate feedback that is so crucial to enabling play.

safety first

When you introduce saving, and hence authentication, you need to be very careful about running any ol’ program in the browser. The most important thing is that the user code can’t access the cookies (i.e. the login data) of the application. If it could, then a malicious user could make a mean piece of code that deletes your gists when you load their code snippet during your session. There are several summaries of playing it safe with iframes, paying particular attention to the sandbox attribute. For Building Bl.ocks I used only the allow-scripts sandbox attribute. This means the iframe can run JavaScript, but it doesn’t have access to the parent frame. Additionally, I am creating the iframe with no origin, and loading its content via a dataURL. This way I can dynamically update it’s contents directly in the browser without loading anything from another subdomain (which I did in Tributary).

expectations

My sandbox is ready to load any arbitrary HTML code and display a visualization, much the way bl.ocks.org does. The difference is that I want users to be able to edit their code and watch the changes reflected in our sandbox. This presents a few major issues with the way people expect to write their code.

  1. People want to load scripts and stylesheets using relative URLs like:
<script src=”satellite.js”></script>
<link rel=”stylesheet” type=”text/css” href=”style.css”>

2. People want to load data via Ajax like:

d3.json(“data.json”, function(err, data) { … })

Both of these expectations are very reasonable, not only do they work in bl.ocks.org, it is also how things would work if you copy-pasted (or did a git clone) to your local file system and ran the code via a local web server.

this example uses all 3 ways of loading files via relative URLS

technical solutions

How do we fulfill these expectations when our goal is to avoid any trips to the server, and the most straightforward way to accomplish this would be to serve up files from the same domain we are hosting our app from? Our iframe doesn’t even have an origin! The answer involves two very dirty sounding solutions, but when the going gets tough, the tough get going.

Let’s start with handling the <script> and <link> tags. There are two key factors to this feature, we know what files the user might want to load and we can change the user’s code before we put it in the iframe. We know what files the user might load because the files are stored in the gist that backs the user’s code. With this in mind, we can use regular expressions to replace references to the file with the actual contents of the file itself! Admittedly, this approach is a bit brittle, but it should work great in 99% of use cases. I’m of the belief that 99% is a lot better than 0%, so I’ll live with it until a better solution presents itself.

The case of dynamically loading data via Ajax is a bit more tricky, but luckily its solution is a bit more robust. When the user’s code wants to load a file we know is in the gist, we want to return the contents of the file rather than go and fetch the remote file. This enables us to re-render the iframe whenever the user edits one of their data files. Of course we still want to let other Ajax requests work as normal too, so how do we accomplish this?

We overload the XMLHttpRequest object inside the iframe! We can see what the URL the XHR request is attempting to load, if it matches the name of one of our files, we short-circuit and set the responseText on our fake XHR object to be the file’s contents. If it is a normal request we need to wire up all the potential event handlers from the fake XHR object to the real one.

It’s almost time to start playing! There are still a couple of launch blocking issues to fix before hosting this project and releasing it in the wild. You can get a sense for how it works by watching this walkthru video:

closing this issue fixed the weird “save on adding file” UX shown in this video

--

--

Ian Johnson

pixel flipper. Data Vis Developer @ObservableHQ. formerly @Google @lever