/* * 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.res.AssetFileDescriptor; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresFeature; import androidx.concurrent.futures.CallbackToFutureAdapter; import com.google.common.util.concurrent.ListenableFuture; import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate; import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateCallback; import org.intellij.lang.annotations.Language; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.concurrent.GuardedBy; /** * Environment within a {@link JavaScriptSandbox} where Javascript is executed. * * A single {@link JavaScriptSandbox} process can contain any number of {@link 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 {@link JavaScriptSandbox}. If the code in one * {@link 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 {@link JavaScriptSandbox} processes should be used, but it is not supported * at the moment. Please find the feature request here. *

* Each isolate object must only be used from one thread. */ public final class JavaScriptIsolate implements AutoCloseable { private static final String TAG = "JavaScriptIsolate"; private final Object mSetLock = new Object(); @Nullable private IJsSandboxIsolate mJsIsolateStub; private CloseGuardHelper mGuard = CloseGuardHelper.create(); private final Executor mThreadPoolTaskExecutor = Executors.newCachedThreadPool(new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { return new Thread( r, "JavaScriptIsolate Thread #" + mCount.getAndIncrement()); } }); private final JavaScriptSandbox mJsSandbox; @Nullable @GuardedBy("mSetLock") private HashSet> mPendingCompleterSet = new HashSet>(); private class IJsSandboxIsolateCallbackStubWrapper extends IJsSandboxIsolateCallback.Stub { private CallbackToFutureAdapter.Completer mCompleter; IJsSandboxIsolateCallbackStubWrapper(CallbackToFutureAdapter.Completer completer) { mCompleter = completer; } @Override public void reportResult(String result) { mCompleter.set(result); removePending(mCompleter); } @Override public void reportError(@ExecutionErrorTypes int type, String error) { assert type == IJsSandboxIsolateCallback.JS_EVALUATION_ERROR; mCompleter.setException(new EvaluationFailedException(error)); removePending(mCompleter); } } JavaScriptIsolate(IJsSandboxIsolate jsIsolateStub, JavaScriptSandbox sandbox) { mJsSandbox = sandbox; mJsIsolateStub = jsIsolateStub; mGuard.open("close"); // This should be at the end of the constructor. } /** * Evaluates the given JavaScript code and returns the result. * * There are 3 possible behaviors based on the output of the expression: *

* The environment uses a single JS global object for all the calls to {@link * #evaluateJavaScriptAsync(String)} and {@link #provideNamedData(String, byte[])} 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 {@code