@ThreadSafe
public final class JavaScriptIsolate implements AutoCloseable


Environment within a JavaScriptSandbox where JavaScript is executed.

A single JavaScriptSandbox process can contain any number of JavaScriptIsolate instances where JS can be evaluated independently and in parallel.

Each isolate has its own state and JS global object, and cannot interact with any other isolate through JS APIs. There is only a moderate security boundary between isolates in a single JavaScriptSandbox. If the code in one JavaScriptIsolate is able to compromise the security of the JS engine then it may be able to observe or manipulate other isolates, since they run in the same process. For strong isolation multiple JavaScriptSandbox processes should be used, but it is not supported at the moment. Please find the feature request here.

This class is thread-safe.

Summary

Public methods

void

Add a callback to listen for isolate crashes.

void

Add a callback to listen for isolate crashes.

void
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
clearConsoleCallback()

Clear any JavaScriptConsoleCallback set via setConsoleCallback.

void

Closes the JavaScriptIsolate object and renders it unusable.

@NonNull ListenableFuture<String>
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_EVALUATE_FROM_FD, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
evaluateJavaScriptAsync(@NonNull AssetFileDescriptor afd)

Reads and evaluates the JavaScript code in the file described by the given AssetFileDescriptor.

@NonNull ListenableFuture<String>

Evaluates the given JavaScript code and returns the result.

@NonNull ListenableFuture<String>
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_EVALUATE_FROM_FD, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
evaluateJavaScriptAsync(@NonNull ParcelFileDescriptor pfd)

Reads and evaluates the JavaScript code in the file described by the given ParcelFileDescriptor.

void
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
provideNamedData(@NonNull String name, @NonNull byte[] inputBytes)

Provides a byte array for consumption from the JavaScript environment.

void

Remove a callback previously registered with addOnTerminatedCallback.

void
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
setConsoleCallback(@NonNull JavaScriptConsoleCallback callback)

Set a JavaScriptConsoleCallback to process console messages from the isolate.

void
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
setConsoleCallback(
    @NonNull Executor executor,
    @NonNull JavaScriptConsoleCallback callback
)

Set a JavaScriptConsoleCallback to process console messages from the isolate.

Protected methods

void

Public methods

addOnTerminatedCallback

Added in 1.0.0-beta01
public void addOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback)

Add a callback to listen for isolate crashes.

This is the same as calling addOnTerminatedCallback using the main executor of the context used to create the JavaScriptSandbox object.

Parameters
@NonNull Consumer<TerminationInfo> callback

the consumer to be called with TerminationInfo when a crash occurs

Throws
java.lang.IllegalStateException

if the callback is already registered (using any executor)

addOnTerminatedCallback

Added in 1.0.0-beta01
public void addOnTerminatedCallback(
    @NonNull Executor executor,
    @NonNull Consumer<TerminationInfo> callback
)

Add a callback to listen for isolate crashes.

There is no guaranteed order to when these callbacks are triggered and unfinished evaluations' futures are rejected.

Registering a callback after the isolate has crashed will result in it being executed immediately on the supplied executor with the isolate's TerminationInfo as an argument.

Closing an isolate via close is not considered a crash, even if there are unresolved evaluations, and will not trigger termination callbacks.

Parameters
@NonNull Executor executor

the executor with which to run callback

@NonNull Consumer<TerminationInfo> callback

the consumer to be called with TerminationInfo when a crash occurs

Throws
java.lang.IllegalStateException

if the callback is already registered (using any executor)

clearConsoleCallback

Added in 1.0.0-beta01
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
public void clearConsoleCallback()

Clear any JavaScriptConsoleCallback set via setConsoleCallback.

Clearing a callback is not guaranteed to take effect for any already pending evaluations.

close

Added in 1.0.0-beta01
public void close()

Closes the JavaScriptIsolate object and renders it unusable.

Once closed, no more method calls should be made. Pending evaluations will reject with an IsolateTerminatedException immediately.

If isFeatureSupported is true for JS_FEATURE_ISOLATE_TERMINATION, then any pending evaluations are terminated. If it is false, the isolate will not get cleaned up until the pending evaluations have run to completion and will consume resources until then.

Closing an isolate via this method does not wait on the isolate to clean up. Resources held by the isolate may remain in use for a duration after this method returns.

evaluateJavaScriptAsync

Added in 1.0.0-beta01
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_EVALUATE_FROM_FD, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
public @NonNull ListenableFuture<StringevaluateJavaScriptAsync(@NonNull AssetFileDescriptor afd)

Reads and evaluates the JavaScript code in the file described by the given AssetFileDescriptor.

Please refer to the documentation of evaluateJavaScriptAsync as the behavior of this method is similar other than for the input type.

This API exposes the underlying file to the service. In case the service process is compromised for unforeseen reasons, it might be able to read from the AssetFileDescriptor beyond the given length and offset. This API does not close the given AssetFileDescriptor.

Note: The underlying file data must be UTF-8 encoded.

This overload is useful when the source of the data is easily readable as an AssetFileDescriptor, e.g. an asset or raw resource.

Parameters
@NonNull AssetFileDescriptor afd

an AssetFileDescriptor for a file containing UTF-8 encoded JavaScript code to be evaluated

Returns
@NonNull ListenableFuture<String>

a Future that evaluates to the result String of the evaluation or an exception (see JavaScriptException and subclasses) if there is an error

evaluateJavaScriptAsync

Added in 1.0.0-beta01
public @NonNull ListenableFuture<StringevaluateJavaScriptAsync(@NonNull String code)

Evaluates the given JavaScript code and returns the result.

There are 3 possible behaviors based on the output of the expression:

  • If the JS expression evaluates to a JS String, then the Java Future resolves to a Java String.
  • If the JS expression evaluates to a JS Promise, and if isFeatureSupported for JS_FEATURE_PROMISE_RETURN returns true, the Java Future resolves to a Java String once the promise resolves. If it returns false, then the Future resolves to an empty Java string.
  • If the JS expression evaluates to another data type, then the Java Future resolves to an empty Java String.
The environment uses a single JS global object for all the calls to evaluateJavaScriptAsync(String) and provideNamedData methods. These calls are queued up and are run one at a time in sequence, using the single JS environment for the isolate. The global variables set by one evaluation are visible for later evaluations. This is similar to adding multiple <script> tags in HTML. The behavior is also similar to evaluateJavascript.

If isFeatureSupported for JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT returns false, the size of the expression to be evaluated and the result/error value is limited by the binder transaction limit (android.os.TransactionTooLargeException). If it returns true, they are not limited by the binder transaction limit but are bound by setMaxEvaluationReturnSizeBytes with a default size of DEFAULT_MAX_EVALUATION_RETURN_SIZE_BYTES.

Do not use this method to transfer raw binary data. Scripts or results containing unpaired surrogate code units are not supported.

Parameters
@NonNull String code

JavaScript code to evaluate. The script should return a JavaScript String or, alternatively, a Promise that will resolve to a String if JS_FEATURE_PROMISE_RETURN is supported.

Returns
@NonNull ListenableFuture<String>

a Future that evaluates to the result String of the evaluation or an exception (see JavaScriptException and subclasses) if there is an error

evaluateJavaScriptAsync

Added in 1.0.0-beta01
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_EVALUATE_FROM_FD, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
public @NonNull ListenableFuture<StringevaluateJavaScriptAsync(@NonNull ParcelFileDescriptor pfd)

Reads and evaluates the JavaScript code in the file described by the given ParcelFileDescriptor.

Please refer to the documentation of evaluateJavaScriptAsync as the behavior of this method is similar other than for the input type.

This API exposes the underlying file to the service. In case the service process is compromised for unforeseen reasons, it might be able to read from the ParcelFileDescriptor beyond the given length and offset. This API does not close the given ParcelFileDescriptor.

Note: The underlying file data must be UTF-8 encoded.

This overload is useful when the source of the data is easily readable as a ParcelFileDescriptor, e.g. a file from shared memory or the app's data directory.

Parameters
@NonNull ParcelFileDescriptor pfd

a ParcelFileDescriptor for a file containing UTF-8 encoded JavaScript code that is evaluated

Returns
@NonNull ListenableFuture<String>

a Future that evaluates to the result String of the evaluation or an exception (see JavaScriptException and subclasses) if there is an error

provideNamedData

Added in 1.0.0-beta01
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
public void provideNamedData(@NonNull String name, @NonNull byte[] inputBytes)

Provides a byte array for consumption from the JavaScript environment.

This method provides an efficient way to pass in data from Java into the JavaScript environment which can be referred to from JavaScript. This is more efficient than including data in the JS expression, and allows large data to be sent.

This data can be consumed in the JS environment using android.consumeNamedDataAsArrayBuffer(String) by referring to the data with the name that was used when calling this method. This is a one-time transfer and the calls should be paired.

A single name can only be used once in a particular JavaScriptIsolate. Clients can generate unique names for each call if they need to use this method multiple times. The same name should be included into the JS code.

This API can be used to pass a WASM module into the JS environment for compilation if isFeatureSupported returns true for JS_FEATURE_WASM_COMPILATION. In Java,

    jsIsolate.provideNamedData("id-1", byteArray);
In JS,
    android.consumeNamedDataAsArrayBuffer("id-1").then((value) => {
      return WebAssembly.compile(value).then((module) => {
         ...
      });
    });

The environment uses a single JS global object for all the calls to evaluateJavaScriptAsync and provideNamedData(String, byte[]) methods.

This method should only be called if isFeatureSupported returns true for JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER.

Parameters
@NonNull String name

identifier for the data that is passed. The same identifier should be used in the JavaScript environment to refer to the data.

@NonNull byte[] inputBytes

bytes to be passed into the JavaScript environment. This array must not be modified until the JavaScript promise returned by consumeNamedDataAsArrayBuffer has resolved (or rejected).

Throws
java.lang.IllegalStateException

if the name has previously been used in the isolate

removeOnTerminatedCallback

Added in 1.0.0-beta01
public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback)

Remove a callback previously registered with addOnTerminatedCallback.

Parameters
@NonNull Consumer<TerminationInfo> callback

the callback to unregister, if currently registered

setConsoleCallback

Added in 1.0.0-beta01
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
public void setConsoleCallback(@NonNull JavaScriptConsoleCallback callback)

Set a JavaScriptConsoleCallback to process console messages from the isolate.

This is the same as calling setConsoleCallback using the main executor of the context used to create the JavaScriptSandbox object.

Parameters
@NonNull JavaScriptConsoleCallback callback

the callback implementing console logging behaviour

setConsoleCallback

Added in 1.0.0-beta01
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING, enforcement = "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
public void setConsoleCallback(
    @NonNull Executor executor,
    @NonNull JavaScriptConsoleCallback callback
)

Set a JavaScriptConsoleCallback to process console messages from the isolate.

Scripts always have access to console APIs, regardless of whether a console callback is set. By default, no console callback is set and calling a console API from JavaScript will do nothing, and will be relatively cheap. Setting a console callback allows console messages to be forwarded to the embedding application, but may negatively impact performance.

Note that console APIs may expose messages generated by both JavaScript code and V8/JavaScriptEngine internals.

Use caution if using this in production code as it may result in the exposure of debugging information or secrets through logs.

When setting a console callback, this method should be called before requesting any evaluations. Calling setConsoleCallback after requesting evaluations may result in those pending evaluations' console messages being dropped or logged to a previous console callback.

Note that delayed console messages may continue to be delivered after the isolate has been closed (or has crashed).

Parameters
@NonNull Executor executor

the executor for running callback methods

@NonNull JavaScriptConsoleCallback callback

the callback implementing console logging behaviour

Protected methods

finalize

protected void finalize()