Now that you know how to provide services to guest code, you can practice calling guest code back from the host.
Create guest code
Assume your third-party guest code is as follows (also available here):
<html> <head></head> <body> <div id="output"></div> <script type="text/javascript"> function timerFired(event) { document.getElementById('output').innerHTML += 'Got timer event at time ' + event.time + '<br/>'; } timerService.registerListener(timerFired); </script> </body> </html>
This code expects the host to provide an object
called timerService
containing a method
called registerListener
. The guest code passes a callback
function of its own to that method, and the host code calls that
function back periodically with a parameter, event
, that
contains a property called time
.
Create host page with services
Now create your host page like this (also available here):
<html> <head> <title>Caja host page</title> <script type="text/javascript" src="//caja.appspot.com/caja.js"> </script> </head> <body> <h1>Caja host page</h1> <div id="guest"></div> <script type="text/javascript"> caja.initialize({ cajaServer: 'https://caja.appspot.com/', debug: true }); caja.load(document.getElementById('guest'), undefined, function(frame) { var listeners = []; var timerService = { // (1) registerListener: function(l) { listeners.push(l); } }; caja.markReadOnlyRecord(timerService); // (2) caja.markFunction(timerService.registerListener); var tamedTimerService = caja.tame(timerService); function callListeners() { // (3) var event = { time: '' + new Date() }; // (4) caja.markReadOnlyRecord(event); for (var i = 0; i < listeners.length; i++) { listeners[i](event); // (5) } } setInterval(callListeners, 1000); // (6) frame.code('https://developers.google.com/caja/demos/callingguestcode/guest.html', 'text/html') .api({ timerService: tamedTimerService }) .run(); }); </script> </body> </html>
Though simple, this example demonstrates quite a few aspects of constructing a two-way interface between host and guest code:
(1) the timerService
object is declared.
(2) the caja
object is used to tame the timer
service. We want the top-level object to be read-only (we don't want
guest code to mess with it), and we want to allow guest code to call
the registerListener
function.
(3) the callListeners
function is defined; it
will be called periodically by the setInterval
at
(6).
(4) an event
object is constructed to pass to the
guest code. Note that this too must be tamed. Specifically, imagine
that we had several pieces of guest code using this service. To
prevent one guest from damaging information seen by another, we must
protect the event
object from tampering. We do so by
making it a read-only record and ensuring that its time
field is a string
, which cannot be modified.
(5) the guest code function is actually called here. It is
invoked like a regular function; Caja will implicitly tame(...)
its arguments so that the guest code gets only the tamed view of the event.
(6) we invoke the guest code as before, passing the tamed API.
Loading the host page and pressing the button should give you a result like this:

Run it yourself
Review
- You created an API such that guest code and host code could call each other; and
- You learned to reason about how your API could be used as an attack vector and the steps you can take to avoid that.