Calling back guest code

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.

Next steps