Illustrative Examples

This chapter presents several examples that showcase Stopify’s features.

A Blocking sleep Function

The browser does not have a blocking sleep function. However, we can use window.setTimeout and Stopify to simulate a blocking sleep operation:

function sleep(duration) {
  asyncRun.pauseImmediate(() => {
    window.setTimeout(() => asyncRun.continueImmediate({ type: 'normal', value: undefined }), duration);
  });
}

In the code above, asyncRun is an instance of AsyncRun (The AsyncRun Interface). Note that this function should be stopified itself and needs to be declared as an external. A complete example of a page that uses sleep is shown below.

<html>
  <body>
    <script src="../../stopify/dist/stopify-full.bundle.js"></script>
    <script>
    function sleep(duration) {
      asyncRun.pauseImmediate(() => {
        window.setTimeout(() => asyncRun.continueImmediate({ type: 'normal', value: undefined }), duration);
      });
    }

    const program = `
      while(true) {
        sleep(1000);
        document.body.appendChild(document.createTextNode("."));
      }
    `;

    const asyncRun = stopify.stopifyLocally(
      program,
      { externals: [ "sleep", "document" ] });

    asyncRun.run(() => { });
    </script>
  </body>
</html>

This program runs forever and prints a period each second.

A Blocking prompt Function

The prompt and alert functions that are built-in to browsers are not the ideal way to receive input from the user. First, modal dialog boxes are unattractive; second, a user can dismiss them; and finally, if a page displays too many modal dialog boxes, the browser can give the user to suppress all of them.

<html>
<body>
  <div id="webConsole"></div>
  <script src="../../stopify/dist/stopify-full.bundle.js"></script>
  <script>
    const webConsole = document.getElementById('webConsole');

    function alert(message) {
      const div = document.createElement('div');
      div.appendChild(document.createTextNode(message));
      webConsole.appendChild(div);
    }

    function prompt() {
      return runner.pauseImmediate(() => {
        const div = document.createElement('div');
        div.appendChild(document.createTextNode('> '));
        const input = document.createElement('input');
        div.appendChild(input);
        // When ENTER pressed, replace the <input> with plain text
        input.addEventListener('keypress', (event) => {
          if (event.keyCode === 13) {
            const value = input.value;
            div.appendChild(document.createTextNode(value));
            div.removeChild(input);
            runner.continueImmediate({ type: 'normal', value: value });
          }
        });
        webConsole.appendChild(div);
        input.focus();
      });
    }

    const program = `
      alert("Enter the first number");
      var x = Number(prompt());
      alert("Enter the second number");
      var y = Number(prompt());
      alert("Result is " + (x + y));
    `;

    const runner = stopify.stopifyLocally(
      program,
      { externals: [ 'prompt', 'alert' ] });

    runner.run(() => console.log('program complete'));
  </script>
</body>
</html>

This program prompts the user for two inputs without modal dialog boxes.

External Events

<html>
<body>
  <button id="pauseResume">Pause / Resume</button>
  <div id="webConsole"></div>
  <script src="../../stopify/dist/stopify-full.bundle.js"></script>
  <script>
    const webConsole = document.getElementById('webConsole');

    var paused = false;
    document.getElementById('pauseResume').addEventListener('click', () => {
      if (paused) {
        paused = false;
        runner.resume();
      }
      else {
        runner.pause(() => {
          paused = true;
        });
      }
    });

    function alert(message) {
      const div = document.createElement('div');
      div.appendChild(document.createTextNode(message));
      webConsole.appendChild(div);
    }

    function onClick(callback) {
      window.addEventListener('click', evt => {
        runner.processEvent(
          () => callback(evt.clientX, evt.clientY),
          () => { /* result from callback does not matter */ });
      });
    }

    const program = `
      onClick(function(x, y) {
        alert('You clicked at (' + x + ', ' + y + ')');
      });
    `;

    const runner = stopify.stopifyLocally(
      program,
      { externals: [ 'onClick', 'alert' ] });

    runner.run(() => console.log('program complete'));
  </script>
</body>
</html>

The onClick function in the code below is an example of an event handler that receives a stopified callback named callback. However, onClick cannot apply callback directly. To support suspending execution, callback must execute in the context of Stopify’s runtime system.

Instead, onClick invokes Stopify’s processEvent method, passing it a thunk that calls callback. If the program is paused, processEvent queues the event until the program is resumed. Therefore, if the user pauses the program, clicks several times, and then resumes, several clicks will clicks will occur immediately after resumption. It is straightforward to discard clicks that occur while paused by testing the paused variable before invoking processEvent.