On January 31st, 2021, we will be archiving the Caja project. After January 31, no new features will be added, pull requests and other issues will no longer be addressed, including patches for security issues, and the repository will be marked as archived. Caja has not been actively maintained or developed to keep up with the latest research on web security. As a result, several security vulnerabilities have been reported to Caja, both by Google’s security engineers and by external researchers.

We encourage users of Caja's HTML and CSS sanitizers to migrate to Closure toolkit, an open source toolkit for Javascript. Closure is used by applications, including Search, Gmail, Docs and Maps.

The Closure library has built-in HTML and CSS sanitizers and provides native support for key security mitigations like Content Security Policy and Trusted Types. Additionally, Closure templates provide a strictly contextual auto-escaping system, which can drastically reduce the risk of XSS in your application.

Waiting for Caja to be ready

As you have seen before, you use the caja object to tame host objects for sharing with guest code. However, the taming facilities of the caja object are not immediately available. In our previous examples of host code, we have used the following pattern:

caja.initialize({ /* ... */ });
caja.load(aDiv, aUriPolicy, function(frame) {
  // 'caja' object is ready for taming here
});

taking advantage of the fact that Caja does not invoke the callback passed to load until the caja object is ready. Sometimes, this will not be enough; you may want to do some shared setup outside a specific load call. Here, we show you an example of how to do that.

Create guest code

Assume you define some guest JavaScript as follows (also available here):

<html>
  <head></head>
  <body>
    <input type="button" id="theButton" value="Say hello">
    <script type="text/javascript">
      document.getElementById('theButton').onclick = function() {
        sayHello();
      };     
    </script>
  </body>
</html>

Create host page

Now create a host page that wishes to load two instances of this guest, giving the same tamed API (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="guest0" style="height: 200px;"></div>
    <div id="guest1" style="height: 200px;"></div>
    <script type="text/javascript">
      caja.initialize({
        cajaServer: 'https://caja.appspot.com/',
        debug: true
      });

      function alertGreeting() { alert('Hello world'); };
      var tamedAlertGreeting;

      caja.whenReady(function() {  // (1)
        caja.markFunction(alertGreeting);
        tamedAlertGreeting = caja.tame(alertGreeting);
      });

      function loadGuest(divId) {
        caja.load(document.getElementById(divId), undefined, function(frame) {  // (2)
          frame.code('https://developers.google.com/caja/demos/whenreadycallback/guest.html',
                     'text/html')
               .api({ sayHello: tamedAlertGreeting })
               .run();
        });
      }

      loadGuest('guest0');
      loadGuest('guest1');
    </script>
  </body>
</html>

Caja will call the argument of whenReady at (1) before it honors any pending requests to load as in (2). This example uses that fact to make sure tamedAlertGreeting is available for both guests when the callbacks of load are invoked. The resulting page looks like the following:

Run it yourself

Review

  • You learned to register callbacks that defer until caja is ready to be used.

Next steps