How the live editor works

Pamela Fox edited this page May 14, 2015 · 9 revisions

The basic setup

The live editor consists of two main components:

The code editor is on the left hand side, and by default, it is powered by the ACE editor. Other editors can also be plugged in (like the structured-blocks editor).

The output is on the right hand side in an iframe, and it executes code (either ProcessingJS code, HTML/CSS/JS, or SQL) . On Khan Academy, that iframe is on a separate domain so that users can't access Khan Academy user credentials. Be aware of that if running this on an authenticated domain.

Diagram of live editor

Everything is kicked off by instantiating a new LiveEditor() on a page, giving it a div to live inside, and specifying paths if necessary:

var options = {
    el: $("#sample-live-editor"),
    execDir: "../../js/exec/",
    execFile: "../../exec.html",
    externalsDir: "../../external/",
    imagesDir: "../../images/"

window.liveEditor = new LiveEditor(options);

The life cycle of a code edit

This is the life cycle of a code edit for the ProcessingJS environment, there are some differences in HTML/CSS and SQL:

  1. When the user types in code, that triggers the ACE editor's "change" event. See LiveEditor.bind in live-editor.js
  2. LiveEditor posts a message to the exec iframe with the current code. See LiveEditor.runCode in live-editor.js
  3. When the exec iframe receives a message with code in it, it will send it through a series of steps to check for errors. See Output.runCode in output.js
  4. It will first send the code to the JSHint worker. That worker will respond back with any encountered errors.
  5. It will then send the code through BabyHint (not in a worker, as it's not super intensive), and will merge together JSHint errors and BabyHint errors.
  6. It will then send the code and any associated tests (like for coding challenges) to the test worker.
  7. If there are hint-related errors, it will display them using Output.handleError. Otherwise, it will proceed to sending it to the associated output mechanism.
  8. When the code gets sent to PJSOutput, it goes through a ProcessingJS-specific process. See PJSOutput.runCode in output.js
  9. It first caches any images found in the code using the PJSResourceCache.
  10. Then it sends the code to the ProcessingJS worker, which returns an error if the code is perceived to be "slow-running."
  11. If the worker doesn't report back in a given amount of time, it assumes that it has an infinite loop and reports an error to the user.
  12. If there are no errors at this point, then it executes the code using PJSOutput.injectCode

Deep dive on the output components

JSHint Worker

See js/workers/pjs/jshint-worker.js, external/jshint/jshint.js, external/es5-shim/es5-shim.js

The JSHint worker delay loads its dependencies (ES5-shim and JSHint), due to the need on Khan Academy to communicate the user's language. Our JSHint is a custom fork both because it has translated message strings and has more friendly, helpful messages.


See js/output/pjs/babyhint.js

The BabyHint library does an additional series of checks that JSHint does not do:

  • It checks for misspellings of functions/variable names, and throws an error suggesting the correct spelling based on ProcessingJS keywords and program variables.
  • It throws an error if the user from using banned properties like document and location.
  • It throws an error if the user declares a function in the function name(){} style instead of var name=function(){}, as we only support the latter.
  • It checks for trailing equal signs, a common error.
  • It checks for no whitespace after var and before a variable name, a common error.

Test Worker

See: js/workers/pjs/test-worker.js, js/output/shared/output-tester.js, external/structuredjs/structured.js

This worker is used for checking user code against specified tests. This is used in the coding challenges in the Khan Academy curriculum, to help the student practice what they've learnt and get feedback about how well they're doing.

The tests are written using an interface defined by OutputTester in output-tester.js, and detailed in this guide for challenge creators. Basically, they look for particular structures in the code using the StructuredJS library, and decide to either accept the code or give a hint about what's wrong with the particular structure.

Once the worker gets the results from OutputTester, it will return an array of test results and any errors encountered along the way.

ProcessingJS Worker

See: js/workers/pjs/worker.js, js/workers/pjs/processing-stubs.js

This worker includes a stubs file for ProcessingJS, since we can't use <canvas> in workers. It calculates an estimated runtime cost for the code, by assigning a minimal cost to each non-canvas-related function and a higher cost to canvas-related functions, since canvas paints are more expensive. It then runs the code, and sends back an error if the total estimated runtime cost is above a particular threshold.

ProcessingJS Code Injection

See: js/output/pjs/pjs-output.js

Once the code has gone through all the checks and is error-free, it's ready to be executed by Output.injectCode. If it's the first time the code has been injected, it's just executed normally. If there's a code change and another call to Output.injectCode, then the function decides what aspects of the code to dynamically inject. That way, users can change the contents of the draw function and just see the effect of that on the current state of animation.

For more details, watch this talk from EmpireJS or read the extensive comments in Output.injectCode.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.