/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.javascriptengine;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringDef;
import androidx.annotation.VisibleForTesting;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.PackageInfoCompat;
import androidx.javascriptengine.common.Utils;
import com.google.common.util.concurrent.ListenableFuture;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxService;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
* Sandbox that provides APIs for JavaScript evaluation in a restricted environment.
* <p>
* JavaScriptSandbox represents a connection to an isolated process. The isolated process is
* exclusive to the calling app (i.e. it doesn't share anything with, and can't be compromised by
* another app's isolated process).
* <p>
* Code that is run in a sandbox does not have any access to data
* belonging to the original app unless explicitly passed into it by using the methods of this
* class. This provides a security boundary between the calling app and the Javascript execution
* environment.
* <p>
* The calling app can have only one isolated process at a time, so only one
* instance of this class can be open at any given time.
* <p>
* It's safe to share a single {@link JavaScriptSandbox}
* object with multiple threads and use it from multiple threads at once.
* For example, {@link JavaScriptSandbox} can be stored at a global location and multiple threads
* can create their own {@link JavaScriptIsolate} objects from it but the
* {@link JavaScriptIsolate} object cannot be shared.
*/
@ThreadSafe
public final class JavaScriptSandbox implements AutoCloseable {
private static final String TAG = "JavaScriptSandbox";
// TODO(crbug.com/1297672): Add capability to this class to support spawning
// different processes as needed. This might require that we have a static
// variable in here that tracks the existing services we are connected to and
// connect to a different one when creating a new object.
private static final String JS_SANDBOX_SERVICE_NAME =
"org.chromium.android_webview.js_sandbox.service.JsSandboxService0";
static final AtomicBoolean sIsReadyToConnect = new AtomicBoolean(true);
private final Object mLock = new Object();
private final CloseGuardHelper mGuard = CloseGuardHelper.create();
@NonNull
@GuardedBy("mLock")
private final IJsSandboxService mJsSandboxService;
// Don't use mLock for the connection, allowing it to be severed at any time, regardless of
// the status of the main mLock. Use an AtomicReference instead.
//
// The underlying ConnectionSetup is nullable, and is null iff the service has been unbound
// (which should also imply dead or closed).
@NonNull
private final AtomicReference<ConnectionSetup> mConnection;
@NonNull
private final Context mContext;
@GuardedBy("mLock")
@NonNull
private Set<JavaScriptIsolate> mActiveIsolateSet;
private enum State {
ALIVE,
DEAD,
CLOSED,
}
@GuardedBy("mLock")
@NonNull
private State mState;
final ExecutorService mThreadPoolTaskExecutor =
Executors.newCachedThreadPool(new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "JavaScriptSandbox Thread #" + mCount.getAndIncrement());
}
});
/**
* A client-side feature, which may be conditional on one or more service-side features.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@StringDef(value =
{
JS_FEATURE_ISOLATE_TERMINATION,
JS_FEATURE_PROMISE_RETURN,
JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER,
JS_FEATURE_WASM_COMPILATION,
JS_FEATURE_ISOLATE_MAX_HEAP_SIZE,
JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT,
JS_FEATURE_CONSOLE_MESSAGING,
JS_FEATURE_ISOLATE_CLIENT,
JS_FEATURE_EVALUATE_FROM_FD,
})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER, ElementType.METHOD})
public @interface JsSandboxFeature {
}
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* When this
* feature is present, {@link JavaScriptIsolate#close()} terminates the currently running JS
* evaluation and close the isolate. If it is absent, {@link JavaScriptIsolate#close()} cannot
* terminate any running or queued evaluations in the background, so the isolate continues to
* consume resources until they complete.
* <p>
* Irrespective of this feature, calling {@link JavaScriptSandbox#close()} terminates all
* {@link JavaScriptIsolate} objects (and the isolated process) immediately and all pending
* {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)} futures resolve with
* {@link IsolateTerminatedException}.
*/
public static final String JS_FEATURE_ISOLATE_TERMINATION = "JS_FEATURE_ISOLATE_TERMINATION";
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* When this feature is present, JS expressions may return promises. The Future returned by
* {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)} resolves to the promise's result,
* once the promise resolves.
*/
public static final String JS_FEATURE_PROMISE_RETURN = "JS_FEATURE_PROMISE_RETURN";
/**
* Feature for {@link #isFeatureSupported(String)}.
* When this feature is present, {@link JavaScriptIsolate#provideNamedData(String, byte[])}
* can be used.
* <p>
* This also covers the JS API android.consumeNamedDataAsArrayBuffer(string).
*/
public static final String JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER =
"JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER";
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* This features provides additional behavior to {@link
* JavaScriptIsolate#evaluateJavaScriptAsync(String)} ()}. When this feature is present, the JS
* API WebAssembly.compile(ArrayBuffer) can be used.
*/
public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* When this feature is present,
* {@link JavaScriptSandbox#createIsolate(IsolateStartupParameters)} can be used.
*/
public static final String JS_FEATURE_ISOLATE_MAX_HEAP_SIZE =
"JS_FEATURE_ISOLATE_MAX_HEAP_SIZE";
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* When this feature is present, the script passed into
* {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)} as well as the result/error is
* not limited by the Binder transaction buffer size.
*/
@SuppressWarnings("IntentName")
public static final String JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT =
"JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT";
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* When this feature is present, {@link JavaScriptIsolate#setConsoleCallback} can be used to set
* a {@link JavaScriptConsoleCallback} for processing console messages.
*/
public static final String JS_FEATURE_CONSOLE_MESSAGING = "JS_FEATURE_CONSOLE_MESSAGING";
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* When this feature is present, the service can be provided with a Binder interface for
* calling into the client, independent of callbacks.
*/
static final String JS_FEATURE_ISOLATE_CLIENT =
"JS_FEATURE_ISOLATE_CLIENT";
/**
* Feature for {@link #isFeatureSupported(String)}.
* <p>
* When this feature is present,
* {@link JavaScriptIsolate#evaluateJavaScriptAsync(android.content.res.AssetFileDescriptor)}
* and {@link JavaScriptIsolate#evaluateJavaScriptAsync(android.os.ParcelFileDescriptor)}
* can be used to evaluate JavaScript code of known and unknown length from file descriptors.
*/
public static final String JS_FEATURE_EVALUATE_FROM_FD =
"JS_FEATURE_EVALUATE_FROM_FD";
// This set must not be modified after JavaScriptSandbox construction.
@NonNull
private final HashSet<String> mClientSideFeatureSet;
static class ConnectionSetup implements ServiceConnection {
@Nullable
private CallbackToFutureAdapter.Completer<JavaScriptSandbox> mCompleter;
@Nullable
private JavaScriptSandbox mJsSandbox;
@NonNull
private final Context mContext;
@Override
@SuppressWarnings("NullAway")
public void onServiceConnected(ComponentName name, IBinder service) {
// It's possible for the service to die and already have been restarted before
// we've actually observed the original death (b/267864650). If that happens,
// onServiceConnected will be called a second time immediately after
// onServiceDisconnected even though we already unbound. Just do nothing.
if (mCompleter == null) {
return;
}
IJsSandboxService jsSandboxService =
IJsSandboxService.Stub.asInterface(service);
try {
mJsSandbox = new JavaScriptSandbox(mContext, this, jsSandboxService);
} catch (DeadObjectException e) {
runShutdownTasks(e);
return;
} catch (RemoteException | RuntimeException e) {
runShutdownTasks(e);
throw Utils.exceptionToRuntimeException(e);
}
mCompleter.set(mJsSandbox);
mCompleter = null;
}
// TODO(crbug.com/1297672): We may want an explicit way to signal to the client that the
// process crashed (like onRenderProcessGone in WebView), without them having to first call
// one of the methods and have it fail.
@Override
public void onServiceDisconnected(ComponentName name) {
runShutdownTasks(new RuntimeException(
"JavaScriptSandbox internal error: onServiceDisconnected()"));
}
@Override
public void onBindingDied(ComponentName name) {
runShutdownTasks(
new RuntimeException("JavaScriptSandbox internal error: onBindingDied()"));
}
@Override
public void onNullBinding(ComponentName name) {
runShutdownTasks(
new RuntimeException("JavaScriptSandbox internal error: onNullBinding()"));
}
private void runShutdownTasks(@NonNull Exception e) {
if (mJsSandbox != null) {
Log.e(TAG, "Sandbox has died", e);
mJsSandbox.killImmediatelyOnThread();
} else {
mContext.unbindService(this);
sIsReadyToConnect.set(true);
}
if (mCompleter != null) {
mCompleter.setException(e);
}
mCompleter = null;
}
ConnectionSetup(@NonNull Context context,
@NonNull CallbackToFutureAdapter.Completer<JavaScriptSandbox> completer) {
mContext = context;
mCompleter = completer;
}
}
/**
* Asynchronously create and connect to the sandbox process.
* <p>
* Only one sandbox process can exist at a time. Attempting to create a new instance before
* the previous instance has been closed fails with an {@link IllegalStateException}.
* <p>
* Sandbox support should be checked using {@link JavaScriptSandbox#isSupported()} before
* attempting to create a sandbox via this method.
*
* @param context the Context for the sandbox. Use an application context if the connection
* is expected to outlive a single activity or service.
* @return a Future that evaluates to a connected {@link JavaScriptSandbox} instance or an
* exception if binding to service fails
*/
@NonNull
public static ListenableFuture<JavaScriptSandbox> createConnectedInstanceAsync(
@NonNull Context context) {
Objects.requireNonNull(context);
PackageInfo systemWebViewPackage = WebView.getCurrentWebViewPackage();
// Technically, there could be a few race conditions before/after isSupport() where the
// availability changes, which may result in a bind failure.
if (systemWebViewPackage == null || !isSupported()) {
throw new SandboxUnsupportedException("The system does not support JavaScriptSandbox");
}
ComponentName compName =
new ComponentName(systemWebViewPackage.packageName, JS_SANDBOX_SERVICE_NAME);
int flag = Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE;
return bindToServiceWithCallback(context, compName, flag);
}
/**
* Asynchronously create and connect to the sandbox process for testing.
* <p>
* Only one sandbox process can exist at a time. Attempting to create a new instance before
* the previous instance has been closed will fail with an {@link IllegalStateException}.
*
* @param context the Context for the sandbox. Use an application context if the connection
* is expected to outlive a single activity or service.
* @return a Future that evaluates to a connected {@link JavaScriptSandbox} instance or an
* exception if binding to service fails
*/
@NonNull
@VisibleForTesting
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static ListenableFuture<JavaScriptSandbox> createConnectedInstanceForTestingAsync(
@NonNull Context context) {
Objects.requireNonNull(context);
ComponentName compName = new ComponentName(context, JS_SANDBOX_SERVICE_NAME);
int flag = Context.BIND_AUTO_CREATE;
return bindToServiceWithCallback(context, compName, flag);
}
/**
* Check if JavaScriptSandbox is supported on the system.
* <p>
* This method should be used to check for sandbox support before calling
* {@link JavaScriptSandbox#createConnectedInstanceAsync(Context)}.
*
* @return true if JavaScriptSandbox is supported and false otherwise
*/
public static boolean isSupported() {
PackageInfo systemWebViewPackage = WebView.getCurrentWebViewPackage();
if (systemWebViewPackage == null) {
return false;
}
long versionCode = PackageInfoCompat.getLongVersionCode(systemWebViewPackage);
// The current IPC interface was introduced in 102.0.4976.0 (crrev.com/3560402), so all
// versions above that are supported. Additionally, the relevant IPC changes were
// cherry-picked into M101 at 101.0.4951.24 (crrev.com/3568575), so versions between
// 101.0.4951.24 inclusive and 102.0.4952.0 exclusive are also supported.
return versionCode >= 4976_000_00L
|| (4951_024_00L <= versionCode && versionCode < 4952_000_00L);
}
@NonNull
private static ListenableFuture<JavaScriptSandbox> bindToServiceWithCallback(
@NonNull Context context, @NonNull ComponentName compName, int flag) {
Intent intent = new Intent();
intent.setComponent(compName);
return CallbackToFutureAdapter.getFuture(completer -> {
ConnectionSetup connectionSetup = new ConnectionSetup(context, completer);
if (sIsReadyToConnect.compareAndSet(true, false)) {
try {
boolean isBinding = context.bindService(intent, connectionSetup, flag);
if (isBinding) {
Executor mainExecutor;
mainExecutor = ContextCompat.getMainExecutor(context);
completer.addCancellationListener(
() -> context.unbindService(connectionSetup), mainExecutor);
} else {
context.unbindService(connectionSetup);
sIsReadyToConnect.set(true);
completer.setException(
new RuntimeException("bindService() returned false " + intent));
}
} catch (SecurityException e) {
context.unbindService(connectionSetup);
sIsReadyToConnect.set(true);
completer.setException(e);
}
} else {
completer.setException(
new IllegalStateException("Binding to already bound service"));
}
// Debug string.
return "JavaScriptSandbox Future";
});
}
// We prevent direct initializations of this class.
// Use JavaScriptSandbox.createConnectedInstance().
JavaScriptSandbox(@NonNull Context context, @NonNull ConnectionSetup connectionSetup,
@NonNull IJsSandboxService jsSandboxService) throws RemoteException {
mContext = context;
mConnection = new AtomicReference<>(connectionSetup);
mJsSandboxService = jsSandboxService;
final List<String> features = mJsSandboxService.getSupportedFeatures();
mClientSideFeatureSet = buildClientSideFeatureSet(features);
mActiveIsolateSet = new HashSet<>();
mState = State.ALIVE;
mGuard.open("close");
// This should be at the end of the constructor.
}
/**
* Creates and returns a {@link JavaScriptIsolate} within which JS can be executed with default
* settings.
*
* @return a new JavaScriptIsolate
*/
@NonNull
public JavaScriptIsolate createIsolate() {
return createIsolate(new IsolateStartupParameters());
}
/**
* Creates and returns a {@link JavaScriptIsolate} within which JS can be executed with the
* specified settings.
* <p>
* If the sandbox is dead, this will still return an isolate, but evaluations will fail with
* {@link SandboxDeadException}.
*
* @param settings the configuration for the isolate
* @return a new JavaScriptIsolate
*/
@NonNull
public JavaScriptIsolate createIsolate(@NonNull IsolateStartupParameters settings) {
Objects.requireNonNull(settings);
synchronized (mLock) {
JavaScriptIsolate isolate;
switch (mState) {
case ALIVE:
try {
isolate = JavaScriptIsolate.create(this, settings);
} catch (DeadObjectException e) {
killDueToException(e);
isolate = JavaScriptIsolate.createDead(this,
"sandbox found dead during call to createIsolate");
} catch (RemoteException | RuntimeException e) {
killDueToException(e);
throw Utils.exceptionToRuntimeException(e);
}
break;
case DEAD:
isolate = JavaScriptIsolate.createDead(this,
"sandbox was dead before call to createIsolate");
break;
case CLOSED:
throw new IllegalStateException("Cannot create isolate in closed sandbox");
default:
throw new AssertionError("unreachable");
}
mActiveIsolateSet.add(isolate);
return isolate;
}
}
// In practice, this method should only be called whilst already holding mLock, but it is
// called via JavaScriptIsolate and this constraint cannot be cleanly expressed via GuardedBy.
IJsSandboxIsolate createIsolateOnService(@NonNull IsolateStartupParameters settings,
@Nullable IJsSandboxIsolateClient isolateInstanceCallback) throws RemoteException {
synchronized (mLock) {
assert mState == State.ALIVE;
if (isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT)) {
return mJsSandboxService.createIsolate2(settings.getMaxHeapSizeBytes(),
isolateInstanceCallback);
} else if (isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) {
return mJsSandboxService.createIsolateWithMaxHeapSizeBytes(
settings.getMaxHeapSizeBytes());
} else {
return mJsSandboxService.createIsolate();
}
}
}
@NonNull
private HashSet<String> buildClientSideFeatureSet(@NonNull List<String> features) {
HashSet<String> featureSet = new HashSet<>();
if (features.contains(IJsSandboxService.ISOLATE_TERMINATION)) {
featureSet.add(JS_FEATURE_ISOLATE_TERMINATION);
}
if (features.contains(IJsSandboxService.WASM_FROM_ARRAY_BUFFER)) {
featureSet.add(JS_FEATURE_PROMISE_RETURN);
featureSet.add(JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER);
featureSet.add(JS_FEATURE_WASM_COMPILATION);
}
if (features.contains(IJsSandboxService.ISOLATE_MAX_HEAP_SIZE_LIMIT)) {
featureSet.add(JS_FEATURE_ISOLATE_MAX_HEAP_SIZE);
}
if (features.contains(IJsSandboxService.EVALUATE_WITHOUT_TRANSACTION_LIMIT)) {
featureSet.add(JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT);
}
if (features.contains(IJsSandboxService.CONSOLE_MESSAGING)) {
featureSet.add(JS_FEATURE_CONSOLE_MESSAGING);
}
if (features.contains(IJsSandboxService.ISOLATE_CLIENT)) {
featureSet.add(JS_FEATURE_ISOLATE_CLIENT);
}
if (features.contains(IJsSandboxService.EVALUATE_FROM_FD)) {
featureSet.add(JS_FEATURE_EVALUATE_FROM_FD);
}
return featureSet;
}
/**
* Checks whether a given feature is supported by the JS Sandbox implementation.
* <p>
* The sandbox implementation is provided by the version of WebView installed on the device.
* The app must use this method to check which library features are supported by the device's
* implementation before using them.
* <p>
* A feature check should be made prior to depending on certain features.
*
* @param feature the feature to be checked
* @return {@code true} if supported, {@code false} otherwise
*/
public boolean isFeatureSupported(@JsSandboxFeature @NonNull String feature) {
Objects.requireNonNull(feature);
return mClientSideFeatureSet.contains(feature);
}
void removeFromIsolateSet(@NonNull JavaScriptIsolate isolate) {
synchronized (mLock) {
mActiveIsolateSet.remove(isolate);
}
}
/**
* Closes the {@link JavaScriptSandbox} object and renders it unusable.
* <p>
* The client is expected to call this method explicitly to terminate the isolated process.
* <p>
* Once closed, no more {@link JavaScriptSandbox} and {@link JavaScriptIsolate} method calls
* can be made. Closing terminates the isolated process immediately. All pending evaluations are
* immediately terminated. Once closed, the client may call
* {@link JavaScriptSandbox#createConnectedInstanceAsync(Context)} to create another
* {@link JavaScriptSandbox}. You should still call close even if the sandbox has died,
* otherwise you will not be able to create a new one.
*/
@Override
public void close() {
synchronized (mLock) {
if (mState == State.CLOSED) {
return;
}
unbindService();
sIsReadyToConnect.set(true);
mState = State.CLOSED;
}
notifyIsolatesAboutClosure();
// This is the closest thing to a .close() method for ExecutorServices. This doesn't
// force the threads or their Runnables to immediately terminate, but will ensure
// that once the worker threads finish their current runnable (if any) that the thread
// pool terminates them, preventing a leak of threads.
mThreadPoolTaskExecutor.shutdownNow();
}
/**
* Unbind the service if it hasn't been unbound already.
* <p>
* By itself, this will not put the sandbox into an official dead state, but any subsequent
* interaction with the sandbox will result in a DeadObjectException. As this method does NOT
* trigger ConnectionSetup.onServiceDisconnected or .onBindingDied, it is also useful for
* testing how methods handle DeadObjectException without a race against these callbacks.
* <p>
* This will not, by itself, make JSE ready to create a new sandbox. The JavaScriptSandbox
* object must still be explicitly closed.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@VisibleForTesting
public void unbindService() {
final ConnectionSetup connection = mConnection.getAndSet(null);
if (connection != null) {
mContext.unbindService(connection);
}
}
/**
* Kill the sandbox and immediately update state and trigger callbacks/futures on the calling
* thread.
* <p>
* There is a risk of deadlock if this is called from an isolate-related callback. In order
* to kill from code holding arbitrary locks, use {@link #kill} instead.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@VisibleForTesting
public void killImmediatelyOnThread() {
synchronized (mLock) {
if (mState != State.ALIVE) {
return;
}
mState = State.DEAD;
unbindService();
}
notifyIsolatesAboutDeath();
}
/**
* Kill the sandbox.
* <p>
* This will unbind the sandbox service so that any future IPC will fail immediately.
* However, isolates will be notified asynchronously, from mContext's main executor.
*/
void kill() {
unbindService();
getMainExecutor().execute(this::killImmediatelyOnThread);
}
/**
* Same as {@link #kill}, but logs information about the cause.
*/
void killDueToException(Exception e) {
if (e instanceof DeadObjectException) {
Log.e(TAG, "Sandbox died before or during during remote call", e);
} else {
Log.e(TAG, "Killing sandbox due to exception", e);
}
kill();
}
private void notifyIsolatesAboutClosure() {
// Do not hold mLock whilst calling into JavaScriptIsolate, as JavaScriptIsolate also has
// its own lock and may want to call into JavaScriptSandbox from another thread.
final Set<JavaScriptIsolate> activeIsolateSet;
synchronized (mLock) {
activeIsolateSet = mActiveIsolateSet;
mActiveIsolateSet = Collections.emptySet();
}
for (JavaScriptIsolate isolate : activeIsolateSet) {
final TerminationInfo terminationInfo =
new TerminationInfo(TerminationInfo.STATUS_SANDBOX_DEAD, "sandbox closed");
isolate.maybeSetIsolateDead(terminationInfo);
}
}
private void notifyIsolatesAboutDeath() {
// Do not hold mLock whilst calling into JavaScriptIsolate, as JavaScriptIsolate also has
// its own lock and may want to call into JavaScriptSandbox from another thread.
final JavaScriptIsolate[] activeIsolateSet;
synchronized (mLock) {
activeIsolateSet = mActiveIsolateSet.toArray(new JavaScriptIsolate[0]);
}
for (JavaScriptIsolate isolate : activeIsolateSet) {
isolate.maybeSetSandboxDead();
}
}
@Override
@SuppressWarnings("GenericException") // super.finalize() throws Throwable
protected void finalize() throws Throwable {
try {
mGuard.warnIfOpen();
close();
} finally {
super.finalize();
}
}
@NonNull
Executor getMainExecutor() {
return ContextCompat.getMainExecutor(mContext);
}
}