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.

Running plain JavaScript

Stay organized with collections Save and categorize content based on your preferences.

Caja allows you to run plain JavaScript as well as Web content. For example, you may want to run guest JavaScript without a UI to add third-party numerical formulas, skinnable UIs with custom behaviors, and other application plug-ins that do not fit into the mold of a "virtualized HTML page". Here, we show you how.

Create guest code

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

return function f(x) {
  return Math.cos(5 * x);
};

The return value of the code (i.e., the result you would get if you were to pass the code to new Function and call the result) is a function taking a single numerical variable x and returning a single numerical result.

Create host page

Now create your host page as follows (also available here):

<html>
  <head>
    <title>Caja host page</title>
    <script type="text/javascript" src="//www.google.com/jsapi"></script>
    <script type="text/javascript" src="//caja.appspot.com/caja.js"></script>
    <style type="text/css">
      #graph {
        width: 600px;
        height: 400px;
        margin: 10px;
        border: 1px solid grey;
      }
    </style>
  </head>

  <body>
    <h1>Caja host page</h1>
    <p>  <!-- (1) -->
      Formula URL:
      <input id="formulaUrl" value="https://developers.google.com/caja/demos/runningjavascript/cosine.js" size="100">
      <button id="set">Set</button>
    </p>

    <div id="graph"></div>
    
    <script type="text/javascript">
      var graphDiv = document.getElementById('graph');
    
      google.load('visualization', '1', {packages: ['corechart']});  // (2)

      caja.initialize({
        cajaServer: 'https://caja.appspot.com/',
        debug: true
      });
    
      function series(f, xMin, xMax, npts, data) {  // (3)
        var xStep = (xMax - xMin) / (npts - 1);
        for (var i = 0; i < npts; i++) {
          var x = xMin + (xStep * i);
          var y = f(x);
          data.addRow([x, y]);
        }
      }

      function draw(f) {  // (4)
        var data = new google.visualization.DataTable();
        data.addColumn('number', 'x');
        data.addColumn('number', 'y');
        series(f, -2, 2, 100, data);
        var chart = new google.visualization.ScatterChart(graphDiv);
        chart.draw(data, { width: 600, height: 400 });
      }
   
      function setFormula(formulaUrl) {  // (5)
        graphDiv.innerHTML = 'Loading ' + formulaUrl + ' ...';
        caja.load(undefined, undefined, function(frame) {
            frame.code(formulaUrl, 'application/javascript')  // (6)
                 .run(function (guestF) {  // (7)
                     var f = frame.untame(guestF);  // (8)
                     graphDiv.innerHTML = '';
                     draw(function (x) { return +f(x); });  // (9)
                 });
        });
      }
   
      google.setOnLoadCallback(function() {  // (10)
        document.getElementById('set').onclick = function() {
          setFormula(document.getElementById('formulaUrl').value);
        };
     });
    </script>
  </body>
</html>

(1) We define an HTML input area allowing the user to enter and submit the URL of some JavaScript code.

(2) We load the Google Charts API. We will get a callback at (9) when this API is ready.

(4) This function creates and populates a Google Charts chart, again given some arbitrary numerical function f of one variable. It uses the function series at (3).

(5) Given a URL to some JavaScript code representing a mathematical formula, this function loads it as a Caja module, using the right MIME type at (6), and passes it to the draw function. Contrary to the way we have called run in previous examples, here we pass a callback function to run at (7). This callback function receives the return value of the guest code -- which is, as described previously, a JavaScript function.

Because this function was constructed by the guest code and could misbehave in various ways, we pass it to frame.untame (the opposite of the tame operation used in the preceding example Providing services to guest code) at (8) to obtain a better-behaved wrapper function (which, most significantly, will always frame.tame its arguments before passing them into the underlying guest code).

However, we still must consider misbehavior such as, in this case of graphing, returning a value that isn't a number or even a string. Caja can't help us directly here since this is application-specific, so we do it ourselves; at (9) we make our own wrapper function which uses the unary + operator to ensure that the return value is a number before passing it to draw. We could also have put the + inside of draw.

(10) When the Chart Tools API is ready, we add an onclick handler to the HTML button.

The result of running this code is as follows:

Run it yourself

Review

  • You used JavaScript guest code, rather than HTML; and
  • You learned to obtain and use the return value from the guest code.

Next steps