Starting in Windows 8.1/IE11, Chakra added support for a new set of public hosting APIs called the JavaScript Runtime (JSRT) APIs, which shipped as part of the Windows SDK. While these APIs allowed hosting of Chakra outside the browser, the set of functionality exposed by these APIs during Windows8.1/IE11 was mainly focused on specific server side scenarios across Microsoft products and services like Outlook.com and Azure DocumentDB.
In Windows 10, we have made significant advancements to the JSRT APIs, making them a true first class citizen for hosting Chakra across various application types supported on Windows 10. Developers can now utilize these APIs to host Chakra for scripting in both Classic Windows (Win32) applications and Universal Windows applications. These APIs now provide native access to the underlying Universal Windows Platform (UWP) and ECMAScript 6 support. For the curious, and as an example, the recently announced Node support based on Chakra for Windows 10 IoT devices is built using these APIs.
Design Choices: Scalability, Performance and Resource Throttling
Ever since JSRT APIs were first introduced in IE11/Windows 8.1, they have supported features that help build scalable, reliable and efficient applications and services by hosting Chakra.
Threading flexibility: Rental Threading for Scalability
Multithreading is a common approach to improve parallelism in computing, particularly in server environments where I/O commonly causes apps to pause while waiting for an I/O request to be serviced. However, multithreading imposes significant difficulties on the developer; locks must be used in order to protect critical data structures from being corrupted. COM tackled this problem by creating threading models and then requiring individual components to declare whether they were apartment-threaded (single-threaded) or free-threaded (any thread can interact with it), and then COM would provide automatic binding and marshaling between different threading models.
JSRT APIs utilize a different threading model, called rental-threading. In a rental threading model, a script execution context (defined here) is always single-threaded, but is not hard-bound to the thread it was created on. In other words, at any given time, script operation from one context can be executed in one thread only, but the context can change its thread affinity from time to time. For example, the application may do some work and then decide to relinquish the thread to wait for an I/O request to complete. Once the I/O request completes, the script context can pick any idle thread to continue script execution by using the JsSetCurrentContext API, which allows the script context to start operating again, albeit on another thread.
In a rental-threaded model, hosts could create thread pools to ensure that they do not exceed a particular thread (and CPU) utilization quota. Such a model enables high scalability at low cost as it does not require the application to create a new thread for every request that comes in and requests need not block for other requests to be serviced.
With JSRT APIs, hosts also get the flexibility for managing the runtime’s background operations. Chakra provides a high-performance concurrent JIT compiler and garbage collector. By default, each runtime manages its own set of threads for both background compilation and background garbage collection. Based on the app needs, hosts can provide their own threads for those operations or can force the runtime to perform those tasks on the primary thread, ensuring that applications have predictable behavior and performance characteristics.
Startup and execution efficiency: Script Serialization
To support startup and execution efficiency, JSRT APIs provide the capability of pre-parsing, generating syntax trees and caching the bytecode for a given piece of JavaScript code. For server scenarios in which the same code might be executed perhaps thousands of times, the performance savings this introduces can be substantial.
Consider that a typical execution of a given JavaScript does an initial check for syntax errors, creates an abstract syntax tree, generates bytecode, and then starts executing the outermost scope. Most but the last step can be pre-calculated ahead of time and JSRT APIs supports doing so with the JsSerializeScript function. Once the JavaScript code has been serialized, it can be cached and loaded by the application at any point. Not all hosts will need to do this, but script serialization allows a host balance memory and disk footprint for faster startup performance based on the specific needs of the application.
Resource Throttling: Managing Memory & CPU Utilization
Because JSRT provides in-app JavaScript programmability, the embedded JavaScript can often come from sources other than the application authors. Therefore, a metered and reliable service architecture is required, and JSRT APIs provide mechanisms by which the host process can impose limits on memory consumption and CPU utilization.
Limits on memory utilization
The host can manage the memory limit of any given runtime (a complete JavaScript execution environment with its own isolated garbage collected heap and just-in-time compiler thread – more here) by calling the JsSetRuntimeMemoryLimit function. Hosts should typically set the limit before starting up script, because if called after the runtime has already exceeded the memory limit, the attempt to limit memory usage will fail.
Limits on CPU utilization
Avoiding runaway execution is often scenario specific as the host best knows when to limit or throttle utilization. While there could be valid long running scripts, a malicious script might force running an infinite loop, like below.
(function hang() { | |
while (true) { } | |
})(); |
By default, the JIT code emitted for the above function is optimized for execution speed and does not have any “script interruption” checks inserted by default. By executing the emitted code, a thread will become a runaway, making it impossible to break the loop.
Developers can configure the runtime to allow such loops to be interrupted by passing the AllowScriptInterrupt flag when creating a runtime. When used, a supervisor thread can request that the runtime terminates execution by calling JsDisableRuntimeExecution at any point, which in turn terminates the script runtime and exits any executing code. The runtime can later be re-enabled by using the JsEnableRuntimeExecution function. This provides developers with a straightforward way to terminate script while keeping the script environment alive.
Edge JSRT: Universal Windows Platform & ECMAScript6 support
In Windows10, to align with the evergreen Microsoft Edge browser strategy, Chakra JavaScript engine has forked into two separate components (or binaries)
- Jscript9.dll, a version of Chakra that is supported by IE11 and retains full backward compatibility
- Chakra.dll, a version of Chakra that is supported by Microsoft Edge, will be actively developed and be ever evolving, just like the rendering engine for Microsoft Edge
As a result of this change, JSRT APIs have also split into two
- Legacy JSRT APIs, which use jscript9.dll and provide full backward compatibility for Classic (Win32) Windows applications that were built using JSRT APIs that shipped with Windows8.1/IE11
- Edge JSRT APIs, which use Chakra.dll and become the actively developed JSRT APIs that leverage the continuous performance and language updates that will be coming to Chakra
All of the design choices mentioned in the above section are still supported in Edge JSRT. However, unlike the legacy JSRT APIs, Edge JSRT APIs enable support for hosting Chakra in any Universal Windows applications providing scripts native access to the underlying platform, provide ECMAScript6 support on par with ES6 support enabled by default in Microsoft Edge, and enables debugging of scripts in Universal Windows applications via Visual Studio. The whitepaper on Targeting Edge vs. Legacy Engine with JSRT APIs provides more information about the API split and how to target Edge JSRT APIs.
JSRT and Universal Windows Applications
Universal Windows platform supported by Windows 10 allows developers to use the same code to build applications that run across a family of Windows 10 devices including Desktop, Mobile, Xbox, and IoT. To use Chakra for scriptability in Universal Windows applications, developers can use all the Edge JSRT APIs, with the exception of the following profiling APIs – JsStartProfiling, JsStopProfiling, JsEnumerateHeap, and JsIsEnumeratingHeap, which are currently supported only in Classic (Win32) Windows applications.
The sample JavaScript console app showcases the use of Edge JSRT APIs in a Universal Windows application.
Edge JSRT APIs also allow scripts to natively access UWP APIs. An example of this feature is the uwp npm package that allows UWP access in Node.js apps running on Windows 10 using Chakra. The JsProjectWinRTNamespace allows exposing a UWP namespace to scripts. The following example utilizes UWP APIs to create an http client to get content from Uri:
JsProjectWinRTNamespace(L"Windows.Foundation"); | |
JsProjectWinRTNamespace(L"Windows.Web"); | |
// Get content from an Uri. | |
JsRunScript( | |
L"var uri = new Windows.Foundation.Uri(\"http://somedatasource.com\"); " \ | |
L"var httpClient = new Windows.Web.Http.HttpClient();" \ | |
L"httpClient.getStringAsync(uri).done(function () { " \ | |
L" // do something with the string content " \ | |
L"}, onError); " \ | |
L"function onError(reason) { " \ | |
L" // error handling " \ | |
L"}", JS_SOURCE_CONTEXT_NONE, L"", &result); |
While Universal Windows applications have full support, to use asynchronous UWP APIs and events in a Classic (Win32) Windows application, a COM initialized delegate pumping mechanism needs to be enabled through JsSetProjectionEnqueueCallback. A sample is provided here.
ECMAScript 6 Language Support
ECMA-262 6th Edition (ES6) is the most significant update to the JavaScript language ever and brings various new capabilities and syntactic sugar to the language. By leveraging the ES6 functionality that is available in Chakra for Microsoft Edge, Edge JSRT allows executing simpler and more powerful JavaScript code like ES6 Arrow Function to write lexically-bound single-line anonymous functions and ES6 Promises to perform asynchronous tasks.
Scripts using ES6 language features require no setup to run with Edge JSRT, except for ES6 Promises. For ES6 promises, a hosting application needs to provide an EnqueueJob abstract operation to queue up the promise tasks. JsSetPromiseContinuationCallback allows application to provide an EnqueueJob style callback to process the promise task queue. The following sample showcases how to store the promise tasks and execute them after the current execution context is finished:
static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState) | |
{ | |
// save async task in the callback. | |
*(void **)callbackState = task; | |
} | |
void runPromiseSample() | |
{ | |
JsValueRef result; | |
JsValueRef task = JS_INVALID_REFERENCE; | |
JsValueRef callback = JS_INVALID_REFERENCE; | |
JsSetPromiseContinuationCallback(PromiseContinuationCallback, &callback); | |
JsRunScript( | |
L"//The JavaScript ES6 Promise code goes here" \ | |
L"new Promise(" \ | |
L" function(resolve, reject) {resolve('basic:success');}" \ | |
L").then(function () {return new Promise(" \ | |
L" function(resolve, reject) {resolve('second:success')}" \ | |
L")});", JS_SOURCE_CONTEXT_NONE, L"", &result); | |
// execute async tasks stored in callback | |
while (callback! = JS_INVALID_REFERENCE) { | |
task = callback; | |
callback = JS_INVALID_REFERENCE; | |
JsCallFunction(task, nullptr, 0, &result); | |
} | |
} |
Apart from providing ES6 support for scripts, Edge JSRT also provides APIs to maneuver new ES6 types and objects, such as Symbol or TypedArray, in native code for better JS/native integration and performance.
Debugging applications
JSRT APIs support script debugging with Visual Studio. In legacy JSRT, the host needs to provide an IDebugApplication pointer to put the script context in debug mode. In Edge JSRT, instead of using IDebugApplication, scripts can be debugged using the following steps:
- In Visual Studio, right click on your project -> Select “Properties” -> Select “Debugging” -> In “Debugger Type”, select “Script Only” or “Script and Native”.
- In your application, after creating an execution context, call JsStartDebugging to put the current context in debug mode. When working with multiple contexts, JsStartDebugging should be called every time a new context is set as current context.
- Debug the script!
Summary
We are excited to share some of the JSRT features that have been long completed – rental threading, script serialization, memory & CPU throttling, and the more recent Edge JSRT specific updates – availability in Universal Windows Applications, native UWP access and ECMAScript 6 language support. These changes put together make Chakra a great option to add scriptability to applications and services running on Windows10.
The JSRT APIs with all features mentioned above are available in the latest Windows 10 Insider Preview and Windows SDK, which can be download from here. A sample Universal Windows application and the JSRT API documentation are resources that might be helpful to get started. We are eager to see applications use Chakra for scriptability and would love to hear your feedback to advance these APIs. You could reach us and share your feedback on Twitter at @MSEdgeDev or on Connect. Thanks!
– Limin Zhu, Program Manager, Chakra Team
– Gaurav Seth, Principal PM Manager, Chakra Team