1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.adservices.service.js; 18 19 import static com.android.adservices.service.js.JSScriptArgument.arrayArg; 20 import static com.android.adservices.service.js.JSScriptArgument.numericArg; 21 import static com.android.adservices.service.js.JSScriptArgument.recordArg; 22 import static com.android.adservices.service.js.JSScriptArgument.stringArg; 23 import static com.android.adservices.service.js.JSScriptEngine.JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG; 24 import static com.android.adservices.service.js.JSScriptEngine.JS_SCRIPT_ENGINE_SANDBOX_DEAD_MSG; 25 26 import static com.google.common.truth.Truth.assertThat; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertThrows; 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assume.assumeFalse; 32 import static org.junit.Assume.assumeTrue; 33 import static org.mockito.ArgumentMatchers.any; 34 import static org.mockito.ArgumentMatchers.anyString; 35 import static org.mockito.Mockito.atLeastOnce; 36 import static org.mockito.Mockito.doAnswer; 37 import static org.mockito.Mockito.doNothing; 38 import static org.mockito.Mockito.doThrow; 39 import static org.mockito.Mockito.mock; 40 import static org.mockito.Mockito.never; 41 import static org.mockito.Mockito.reset; 42 import static org.mockito.Mockito.timeout; 43 import static org.mockito.Mockito.verify; 44 import static org.mockito.Mockito.when; 45 46 import android.content.Context; 47 48 import androidx.annotation.NonNull; 49 import androidx.javascriptengine.IsolateStartupParameters; 50 import androidx.javascriptengine.JavaScriptConsoleCallback; 51 import androidx.javascriptengine.JavaScriptIsolate; 52 import androidx.javascriptengine.JavaScriptSandbox; 53 import androidx.javascriptengine.MemoryLimitExceededException; 54 import androidx.javascriptengine.SandboxDeadException; 55 import androidx.test.core.app.ApplicationProvider; 56 import androidx.test.filters.SmallTest; 57 58 import com.android.adservices.LoggerFactory; 59 import com.android.adservices.service.common.NoOpRetryStrategyImpl; 60 import com.android.adservices.service.common.RetryStrategy; 61 import com.android.adservices.service.common.RetryStrategyImpl; 62 import com.android.adservices.service.exception.JSExecutionException; 63 import com.android.adservices.service.profiling.JSScriptEngineLogConstants; 64 import com.android.adservices.service.profiling.Profiler; 65 import com.android.adservices.service.profiling.StopWatch; 66 import com.android.adservices.shared.testing.SdkLevelSupportRule; 67 import com.android.dx.mockito.inline.extended.ExtendedMockito; 68 import com.android.modules.utils.build.SdkLevel; 69 70 import com.google.common.collect.ImmutableList; 71 import com.google.common.io.ByteStreams; 72 import com.google.common.util.concurrent.FluentFuture; 73 import com.google.common.util.concurrent.Futures; 74 import com.google.common.util.concurrent.ListenableFuture; 75 import com.google.common.util.concurrent.ListeningExecutorService; 76 import com.google.common.util.concurrent.MoreExecutors; 77 78 import org.junit.Assume; 79 import org.junit.Before; 80 import org.junit.BeforeClass; 81 import org.junit.Rule; 82 import org.junit.Test; 83 import org.junit.function.ThrowingRunnable; 84 import org.mockito.Mock; 85 import org.mockito.Mockito; 86 import org.mockito.MockitoAnnotations; 87 import org.mockito.MockitoSession; 88 import org.mockito.quality.Strictness; 89 90 import java.io.IOException; 91 import java.io.InputStream; 92 import java.util.Arrays; 93 import java.util.List; 94 import java.util.Objects; 95 import java.util.concurrent.CountDownLatch; 96 import java.util.concurrent.ExecutionException; 97 import java.util.concurrent.ExecutorService; 98 import java.util.concurrent.Executors; 99 import java.util.concurrent.ScheduledThreadPoolExecutor; 100 import java.util.concurrent.TimeUnit; 101 import java.util.concurrent.TimeoutException; 102 import java.util.concurrent.atomic.AtomicBoolean; 103 import java.util.stream.Collectors; 104 105 @SmallTest 106 public class JSScriptEngineTest { 107 108 /** 109 * functions in simple_test_functions.wasm: 110 * 111 * <p>int increment(int n) { return n+1; } 112 * 113 * <p>int fib(int n) { if (n<=1) { return n; } else { return fib(n-2) + fib(n-1); } } 114 * 115 * <p>int fact(int n) { if (n<=1) { return 1; } else { return n * fact(n-1); } } 116 * 117 * <p>double log_base_2(double n) { return log(n) / log(2.0); } 118 */ 119 public static final String WASM_MODULE = "simple_test_functions.wasm"; 120 121 private static final String TAG = JSScriptEngineTest.class.getSimpleName(); 122 123 protected static final Context sContext = ApplicationProvider.getApplicationContext(); 124 private static final Profiler sMockProfiler = mock(Profiler.class); 125 private static final StopWatch sSandboxInitWatch = mock(StopWatch.class); 126 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 127 private static JSScriptEngine sJSScriptEngine; 128 private final ExecutorService mExecutorService = Executors.newFixedThreadPool(10); 129 private final boolean mDefaultIsolateConsoleMessageInLogs = false; 130 private final IsolateSettings mDefaultIsolateSettings = 131 IsolateSettings.forMaxHeapSizeEnforcementDisabled(mDefaultIsolateConsoleMessageInLogs); 132 private final RetryStrategy mNoOpRetryStrategy = new NoOpRetryStrategyImpl(); 133 @Mock JSScriptEngine.JavaScriptSandboxProvider mMockSandboxProvider; 134 @Mock private StopWatch mIsolateCreateWatch; 135 @Mock private StopWatch mJavaExecutionWatch; 136 @Mock private JavaScriptSandbox mMockedSandbox; 137 @Mock private JavaScriptIsolate mMockedIsolate; 138 139 @Rule(order = 0) 140 public final SdkLevelSupportRule sdkLevel = SdkLevelSupportRule.forAtLeastS(); 141 142 @BeforeClass initJavaScriptSandbox()143 public static void initJavaScriptSandbox() { 144 when(sMockProfiler.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME)) 145 .thenReturn(sSandboxInitWatch); 146 doNothing().when(sSandboxInitWatch).stop(); 147 if (JSScriptEngine.AvailabilityChecker.isJSSandboxAvailable()) { 148 sJSScriptEngine = 149 JSScriptEngine.getInstanceForTesting(sContext, sMockProfiler, sLogger); 150 } 151 } 152 153 @Before setup()154 public void setup() { 155 Assume.assumeTrue(JSScriptEngine.AvailabilityChecker.isJSSandboxAvailable()); 156 MockitoAnnotations.initMocks(this); 157 158 reset(sMockProfiler); 159 when(sMockProfiler.start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME)) 160 .thenReturn(mIsolateCreateWatch); 161 when(sMockProfiler.start(JSScriptEngineLogConstants.JAVA_EXECUTION_TIME)) 162 .thenReturn(mJavaExecutionWatch); 163 164 FluentFuture<JavaScriptSandbox> futureInstance = 165 FluentFuture.from(Futures.immediateFuture(mMockedSandbox)); 166 when(mMockSandboxProvider.getFutureInstance(sContext)).thenReturn(futureInstance); 167 } 168 169 @Test testProviderFailsIfJSSandboxNotAvailableInWebViewVersion()170 public void testProviderFailsIfJSSandboxNotAvailableInWebViewVersion() { 171 MockitoSession staticMockSessionLocal = null; 172 173 try { 174 staticMockSessionLocal = 175 ExtendedMockito.mockitoSession() 176 .spyStatic(JavaScriptSandbox.class) 177 .strictness(Strictness.LENIENT) 178 .initMocks(this) 179 .startMocking(); 180 ExtendedMockito.doReturn(false).when(JavaScriptSandbox::isSupported); 181 182 ThrowingRunnable getFutureInstance = 183 () -> 184 new JSScriptEngine.JavaScriptSandboxProvider(sMockProfiler, sLogger) 185 .getFutureInstance(sContext) 186 .get(); 187 188 Exception futureException = assertThrows(ExecutionException.class, getFutureInstance); 189 assertThat(futureException) 190 .hasCauseThat() 191 .isInstanceOf(JSSandboxIsNotAvailableException.class); 192 } finally { 193 if (staticMockSessionLocal != null) { 194 staticMockSessionLocal.finishMocking(); 195 } 196 } 197 } 198 199 @Test testEngineFailsIfJSSandboxNotAvailableInWebViewVersion()200 public void testEngineFailsIfJSSandboxNotAvailableInWebViewVersion() { 201 MockitoSession staticMockSessionLocal = null; 202 203 try { 204 staticMockSessionLocal = 205 ExtendedMockito.mockitoSession() 206 .spyStatic(JavaScriptSandbox.class) 207 .strictness(Strictness.LENIENT) 208 .initMocks(this) 209 .startMocking(); 210 ExtendedMockito.doReturn(false).when(JavaScriptSandbox::isSupported); 211 212 ThrowingRunnable getFutureInstance = 213 () -> 214 callJSEngine( 215 JSScriptEngine.createNewInstanceForTesting( 216 sContext, 217 new JSScriptEngine.JavaScriptSandboxProvider( 218 sMockProfiler, sLogger), 219 sMockProfiler, 220 sLogger), 221 "function test() { return \"hello world\"; }", 222 ImmutableList.of(), 223 "test", 224 mDefaultIsolateSettings, 225 mNoOpRetryStrategy); 226 227 Exception futureException = assertThrows(ExecutionException.class, getFutureInstance); 228 assertThat(futureException) 229 .hasCauseThat() 230 .isInstanceOf(JSSandboxIsNotAvailableException.class); 231 } finally { 232 if (staticMockSessionLocal != null) { 233 staticMockSessionLocal.finishMocking(); 234 } 235 } 236 } 237 238 @Test testCanRunSimpleScriptWithNoArgs()239 public void testCanRunSimpleScriptWithNoArgs() throws Exception { 240 assertThat( 241 callJSEngine( 242 "function test() { return \"hello world\"; }", 243 ImmutableList.of(), 244 "test", 245 mDefaultIsolateSettings, 246 mNoOpRetryStrategy)) 247 .isEqualTo("\"hello world\""); 248 249 verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME); 250 verify(sMockProfiler).start(JSScriptEngineLogConstants.JAVA_EXECUTION_TIME); 251 verify(sSandboxInitWatch).stop(); 252 verify(mIsolateCreateWatch).stop(); 253 verify(mJavaExecutionWatch).stop(); 254 } 255 256 @Test testCanRunAScriptWithNoArgs()257 public void testCanRunAScriptWithNoArgs() throws Exception { 258 assertThat( 259 callJSEngine( 260 "function helloWorld() { return \"hello world\"; };", 261 ImmutableList.of(), 262 "helloWorld", 263 mDefaultIsolateSettings, 264 mNoOpRetryStrategy)) 265 .isEqualTo("\"hello world\""); 266 } 267 268 @Test testCanRunSimpleScriptWithOneArg()269 public void testCanRunSimpleScriptWithOneArg() throws Exception { 270 assertThat( 271 callJSEngine( 272 "function hello(name) { return \"hello \" + name; };", 273 ImmutableList.of(stringArg("name", "Stefano")), 274 "hello", 275 mDefaultIsolateSettings, 276 mNoOpRetryStrategy)) 277 .isEqualTo("\"hello Stefano\""); 278 } 279 280 @Test testCanRunAScriptWithOneArg()281 public void testCanRunAScriptWithOneArg() throws Exception { 282 assertThat( 283 callJSEngine( 284 "function helloPerson(personName) { return \"hello \" + personName;" 285 + " };", 286 ImmutableList.of(stringArg("name", "Stefano")), 287 "helloPerson", 288 mDefaultIsolateSettings, 289 mNoOpRetryStrategy)) 290 .isEqualTo("\"hello Stefano\""); 291 } 292 293 @Test testCanUseJSONArguments()294 public void testCanUseJSONArguments() throws Exception { 295 assertThat( 296 callJSEngine( 297 "function helloPerson(person) { return \"hello \" + person.name; " 298 + " };", 299 ImmutableList.of( 300 recordArg("jsonArg", stringArg("name", "Stefano"))), 301 "helloPerson", 302 mDefaultIsolateSettings, 303 mNoOpRetryStrategy)) 304 .isEqualTo("\"hello Stefano\""); 305 } 306 307 @Test testCanNotReferToScriptArguments()308 public void testCanNotReferToScriptArguments() { 309 ExecutionException e = 310 assertThrows( 311 ExecutionException.class, 312 () -> 313 callJSEngine( 314 "function helloPerson(person) { return \"hello \" +" 315 + " personOuter.name; };", 316 ImmutableList.of( 317 recordArg( 318 "personOuter", 319 stringArg("name", "Stefano"))), 320 "helloPerson", 321 mDefaultIsolateSettings, 322 mNoOpRetryStrategy)); 323 324 assertThat(e).hasCauseThat().isInstanceOf(JSExecutionException.class); 325 } 326 327 // During tests, look for logcat messages with tag "chromium" to check if any of your scripts 328 // have syntax errors. Those messages won't be available on prod builds (need to register 329 // a listener to WebChromeClient.onConsoleMessage to receive them if needed). 330 @Test testWillReturnAStringWithContentNullEvaluatingScriptWithErrors()331 public void testWillReturnAStringWithContentNullEvaluatingScriptWithErrors() { 332 ExecutionException e = 333 assertThrows( 334 ExecutionException.class, 335 () -> 336 callJSEngine( 337 "function test() { return \"hello world\"; }", 338 ImmutableList.of(), 339 "undefinedFunction", 340 mDefaultIsolateSettings, 341 mNoOpRetryStrategy)); 342 343 assertThat(e).hasCauseThat().isInstanceOf(JSExecutionException.class); 344 } 345 346 @Test testParallelCallsToTheScriptEngineDoNotInterfere()347 public void testParallelCallsToTheScriptEngineDoNotInterfere() throws Exception { 348 CountDownLatch resultsLatch = new CountDownLatch(2); 349 350 final ImmutableList<JSScriptArgument> arguments = 351 ImmutableList.of(recordArg("jsonArg", stringArg("name", "Stefano"))); 352 353 ListenableFuture<String> firstCallResult = 354 callJSEngineAsync( 355 "function helloPerson(person) { return \"hello \" + person.name; " + " };", 356 arguments, 357 "helloPerson", 358 resultsLatch, 359 mDefaultIsolateSettings, 360 mNoOpRetryStrategy); 361 362 // The previous call reset the status, we can redefine the function and use the same 363 // argument 364 ListenableFuture<String> secondCallResult = 365 callJSEngineAsync( 366 "function helloPerson(person) { return \"hello again \" + person.name; " 367 + " };", 368 arguments, 369 "helloPerson", 370 resultsLatch, 371 mDefaultIsolateSettings, 372 mNoOpRetryStrategy); 373 374 resultsLatch.await(); 375 376 assertThat(firstCallResult.get()).isEqualTo("\"hello Stefano\""); 377 378 assertThat(secondCallResult.get()).isEqualTo("\"hello again Stefano\""); 379 } 380 381 @Test testCanHandleFailuresFromWebView()382 public void testCanHandleFailuresFromWebView() throws Exception { 383 Assume.assumeFalse(sJSScriptEngine.isLargeTransactionsSupported().get(1, TimeUnit.SECONDS)); 384 385 when(sMockProfiler.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME)) 386 .thenReturn(sSandboxInitWatch); 387 doNothing().when(sSandboxInitWatch).stop(); 388 389 // The binder can transfer at most 1MB, this is larger than needed since, once 390 // converted into a JS array initialization script will be way over the limits. 391 List<JSScriptNumericArgument<Integer>> tooBigForBinder = 392 Arrays.stream(new int[1024 * 1024]) 393 .boxed() 394 .map(value -> numericArg("_", value)) 395 .collect(Collectors.toList()); 396 ExecutionException outerException = 397 assertThrows( 398 ExecutionException.class, 399 () -> 400 callJSEngine( 401 "function helloBigArray(array) {\n" 402 + " return array.length;\n" 403 + "}", 404 ImmutableList.of(arrayArg("array", tooBigForBinder)), 405 "helloBigArray", 406 mDefaultIsolateSettings, 407 mNoOpRetryStrategy)); 408 409 assertThat(outerException).hasCauseThat().isInstanceOf(JSExecutionException.class); 410 // assert that we can recover from this exception 411 assertThat( 412 callJSEngine( 413 "function test() { return \"hello world\"; }", 414 ImmutableList.of(), 415 "test", 416 mDefaultIsolateSettings, 417 new RetryStrategyImpl(1, mExecutorService))) 418 .isEqualTo("\"hello world\""); 419 } 420 421 @Test testCanHandleLargeTransactionsToWebView()422 public void testCanHandleLargeTransactionsToWebView() throws Exception { 423 Assume.assumeTrue(sJSScriptEngine.isLargeTransactionsSupported().get(1, TimeUnit.SECONDS)); 424 List<JSScriptNumericArgument<Integer>> tooBigForBinder = 425 Arrays.stream(new int[1024 * 1024]) 426 .boxed() 427 .map(value -> numericArg("_", value)) 428 .collect(Collectors.toList()); 429 430 String result = 431 callJSEngine( 432 "function helloBigArray(array) {\n" + " return array.length;\n" + "}", 433 ImmutableList.of(arrayArg("array", tooBigForBinder)), 434 "helloBigArray", 435 mDefaultIsolateSettings, 436 mNoOpRetryStrategy); 437 assertThat(Integer.parseInt(result)).isEqualTo(1024 * 1024); 438 } 439 440 @Test testCanCloseAndThenWorkWithSameInstance()441 public void testCanCloseAndThenWorkWithSameInstance() throws Exception { 442 when(sMockProfiler.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME)) 443 .thenReturn(sSandboxInitWatch); 444 doNothing().when(sSandboxInitWatch).stop(); 445 assertThat( 446 callJSEngine( 447 "function test() { return \"hello world\"; }", 448 ImmutableList.of(), 449 "test", 450 mDefaultIsolateSettings, 451 mNoOpRetryStrategy)) 452 .isEqualTo("\"hello world\""); 453 454 sJSScriptEngine.shutdown().get(3, TimeUnit.SECONDS); 455 456 assertThat( 457 callJSEngine( 458 "function test() { return \"hello world\"; }", 459 ImmutableList.of(), 460 "test", 461 mDefaultIsolateSettings, 462 mNoOpRetryStrategy)) 463 .isEqualTo("\"hello world\""); 464 465 // Engine is re-initialized 466 verify(sMockProfiler, atLeastOnce()).start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME); 467 verify(sSandboxInitWatch, atLeastOnce()).stop(); 468 } 469 470 @Test testConnectionIsResetIfJSProcessIsTerminatedWithIllegalStateException()471 public void testConnectionIsResetIfJSProcessIsTerminatedWithIllegalStateException() { 472 when(mMockedSandbox.createIsolate()) 473 .thenThrow( 474 new IllegalStateException( 475 "simulating a failure caused by JavaScriptSandbox being" 476 + " disconnected due to ISE")); 477 478 ExecutionException executionException = 479 callJSEngineAndAssertExecutionException( 480 JSScriptEngine.createNewInstanceForTesting( 481 ApplicationProvider.getApplicationContext(), 482 mMockSandboxProvider, 483 sMockProfiler, 484 sLogger), 485 mDefaultIsolateSettings); 486 487 assertThat(executionException) 488 .hasCauseThat() 489 .isInstanceOf(JSScriptEngineConnectionException.class); 490 assertThat(executionException) 491 .hasMessageThat() 492 .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG); 493 verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME); 494 verify(mMockSandboxProvider).destroyIfCurrentInstance(mMockedSandbox); 495 } 496 497 @Test testConnectionIsResetIfCreateIsolateThrowsRuntimeException()498 public void testConnectionIsResetIfCreateIsolateThrowsRuntimeException() { 499 when(mMockedSandbox.createIsolate()) 500 .thenThrow( 501 new RuntimeException( 502 "simulating a failure caused by JavaScriptSandbox being" 503 + " disconnected")); 504 505 ExecutionException executionException = 506 callJSEngineAndAssertExecutionException( 507 JSScriptEngine.createNewInstanceForTesting( 508 ApplicationProvider.getApplicationContext(), 509 mMockSandboxProvider, 510 sMockProfiler, 511 sLogger), 512 mDefaultIsolateSettings); 513 514 assertThat(executionException) 515 .hasCauseThat() 516 .isInstanceOf(JSScriptEngineConnectionException.class); 517 assertThat(executionException) 518 .hasMessageThat() 519 .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG); 520 verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME); 521 verify(mMockSandboxProvider).destroyIfCurrentInstance(mMockedSandbox); 522 } 523 524 @Test testConnectionIsResetIfEvaluateFailsWithSandboxDeadException()525 public void testConnectionIsResetIfEvaluateFailsWithSandboxDeadException() { 526 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 527 when(mMockedIsolate.evaluateJavaScriptAsync(Mockito.anyString())) 528 .thenReturn(Futures.immediateFailedFuture(new SandboxDeadException())); 529 when(mMockSandboxProvider.destroyIfCurrentInstance(mMockedSandbox)) 530 .thenReturn(Futures.immediateVoidFuture()); 531 532 ExecutionException executionException = 533 callJSEngineAndAssertExecutionException( 534 JSScriptEngine.createNewInstanceForTesting( 535 ApplicationProvider.getApplicationContext(), 536 mMockSandboxProvider, 537 sMockProfiler, 538 sLogger), 539 mDefaultIsolateSettings); 540 541 assertThat(executionException) 542 .hasCauseThat() 543 .isInstanceOf(JSScriptEngineConnectionException.class); 544 assertThat(executionException.getCause()) 545 .hasCauseThat() 546 .isInstanceOf(SandboxDeadException.class); 547 assertThat(executionException).hasMessageThat().contains(JS_SCRIPT_ENGINE_SANDBOX_DEAD_MSG); 548 verify(mMockSandboxProvider).destroyIfCurrentInstance(mMockedSandbox); 549 } 550 551 @Test testEvaluationIsRetriedIfEvaluateFailsWithSandboxDeadException()552 public void testEvaluationIsRetriedIfEvaluateFailsWithSandboxDeadException() throws Exception { 553 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 554 when(mMockedIsolate.evaluateJavaScriptAsync(Mockito.anyString())) 555 .thenReturn(Futures.immediateFailedFuture(new SandboxDeadException())) 556 .thenReturn(Futures.immediateFuture("{\"status\":200}")); 557 when(mMockSandboxProvider.destroyIfCurrentInstance(mMockedSandbox)) 558 .thenReturn(Futures.immediateVoidFuture()); 559 RetryStrategy retryStrategy = new RetryStrategyImpl(1, mExecutorService); 560 assertEquals( 561 callJSEngine( 562 JSScriptEngine.createNewInstanceForTesting( 563 ApplicationProvider.getApplicationContext(), 564 mMockSandboxProvider, 565 sMockProfiler, 566 sLogger), 567 "function test() { return \"hello world\"; }", 568 ImmutableList.of(), 569 "test", 570 mDefaultIsolateSettings, 571 retryStrategy), 572 "{\"status\":200}"); 573 verify(mMockSandboxProvider).destroyIfCurrentInstance(mMockedSandbox); 574 } 575 576 @Test testConnectionIsResetIfEvaluateFailsWithMemoryLimitExceedException()577 public void testConnectionIsResetIfEvaluateFailsWithMemoryLimitExceedException() { 578 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 579 String expectedExceptionMessage = "Simulating Memory limit exceed exception from isolate"; 580 when(mMockedIsolate.evaluateJavaScriptAsync(Mockito.anyString())) 581 .thenReturn( 582 Futures.immediateFailedFuture( 583 new MemoryLimitExceededException(expectedExceptionMessage))); 584 when(mMockSandboxProvider.destroyIfCurrentInstance(mMockedSandbox)) 585 .thenReturn(Futures.immediateVoidFuture()); 586 587 ExecutionException executionException = 588 callJSEngineAndAssertExecutionException( 589 JSScriptEngine.createNewInstanceForTesting( 590 ApplicationProvider.getApplicationContext(), 591 mMockSandboxProvider, 592 sMockProfiler, 593 sLogger), 594 mDefaultIsolateSettings); 595 596 assertThat(executionException).hasCauseThat().isInstanceOf(JSExecutionException.class); 597 assertThat(executionException.getCause()) 598 .hasCauseThat() 599 .isInstanceOf(MemoryLimitExceededException.class); 600 assertThat(executionException).hasMessageThat().contains(expectedExceptionMessage); 601 verify(mMockSandboxProvider).destroyIfCurrentInstance(mMockedSandbox); 602 } 603 604 @Test testConnectionIsNotResetIfEvaluateFailsWithAnyOtherException()605 public void testConnectionIsNotResetIfEvaluateFailsWithAnyOtherException() { 606 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 607 when(mMockedIsolate.evaluateJavaScriptAsync(Mockito.anyString())) 608 .thenReturn( 609 Futures.immediateFailedFuture( 610 new IllegalStateException("this is not SDE"))); 611 when(mMockSandboxProvider.destroyIfCurrentInstance(mMockedSandbox)) 612 .thenReturn(Futures.immediateVoidFuture()); 613 614 ExecutionException executionException = 615 callJSEngineAndAssertExecutionException( 616 JSScriptEngine.createNewInstanceForTesting( 617 ApplicationProvider.getApplicationContext(), 618 mMockSandboxProvider, 619 sMockProfiler, 620 sLogger), 621 mDefaultIsolateSettings); 622 623 assertThat(executionException).hasCauseThat().isInstanceOf(JSExecutionException.class); 624 verify(mMockSandboxProvider, never()).destroyIfCurrentInstance(mMockedSandbox); 625 } 626 627 @Test testEnforceHeapMemorySizeFailureAtCreateIsolate()628 public void testEnforceHeapMemorySizeFailureAtCreateIsolate() { 629 when(mMockedSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) 630 .thenReturn(true); 631 when(mMockedSandbox.createIsolate(Mockito.any(IsolateStartupParameters.class))) 632 .thenThrow( 633 new IllegalStateException( 634 "simulating a failure caused by JavaScriptSandbox not" 635 + " supporting max heap size")); 636 IsolateSettings enforcedHeapIsolateSettings = 637 IsolateSettings.builder() 638 .setEnforceMaxHeapSizeFeature(true) 639 .setMaxHeapSizeBytes(1000) 640 .setIsolateConsoleMessageInLogsEnabled(mDefaultIsolateConsoleMessageInLogs) 641 .build(); 642 643 ExecutionException executionException = 644 callJSEngineAndAssertExecutionException( 645 JSScriptEngine.createNewInstanceForTesting( 646 ApplicationProvider.getApplicationContext(), 647 mMockSandboxProvider, 648 sMockProfiler, 649 sLogger), 650 enforcedHeapIsolateSettings); 651 652 assertThat(executionException) 653 .hasCauseThat() 654 .isInstanceOf(JSScriptEngineConnectionException.class); 655 assertThat(executionException) 656 .hasMessageThat() 657 .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG); 658 verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME); 659 verify(mMockedSandbox) 660 .isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE); 661 } 662 663 @Test testEnforceHeapMemorySizeUnsupportedBySandbox()664 public void testEnforceHeapMemorySizeUnsupportedBySandbox() { 665 when(mMockedSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) 666 .thenReturn(false); 667 IsolateSettings enforcedHeapIsolateSettings = 668 IsolateSettings.builder() 669 .setEnforceMaxHeapSizeFeature(true) 670 .setMaxHeapSizeBytes(1000) 671 .setIsolateConsoleMessageInLogsEnabled(mDefaultIsolateConsoleMessageInLogs) 672 .build(); 673 ExecutionException executionException = 674 callJSEngineAndAssertExecutionException( 675 JSScriptEngine.createNewInstanceForTesting( 676 ApplicationProvider.getApplicationContext(), 677 mMockSandboxProvider, 678 sMockProfiler, 679 sLogger), 680 enforcedHeapIsolateSettings); 681 682 assertThat(executionException) 683 .hasCauseThat() 684 .isInstanceOf(JSScriptEngineConnectionException.class); 685 assertThat(executionException) 686 .hasMessageThat() 687 .contains(JS_SCRIPT_ENGINE_CONNECTION_EXCEPTION_MSG); 688 verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME); 689 } 690 691 @Test testLenientHeapMemorySize()692 public void testLenientHeapMemorySize() throws Exception { 693 // This exception though wired to be thrown will not be thrown 694 when(mMockedSandbox.createIsolate(Mockito.any(IsolateStartupParameters.class))) 695 .thenThrow( 696 new IllegalStateException( 697 "simulating a failure caused by JavaScriptSandbox not" 698 + " supporting max heap size")); 699 IsolateSettings lenientHeapIsolateSettings = 700 IsolateSettings.forMaxHeapSizeEnforcementDisabled( 701 mDefaultIsolateConsoleMessageInLogs); 702 703 assertThat( 704 callJSEngine( 705 "function test() { return \"hello world\"; }", 706 ImmutableList.of(), 707 "test", 708 lenientHeapIsolateSettings, 709 mNoOpRetryStrategy)) 710 .isEqualTo("\"hello world\""); 711 } 712 713 @Test testSuccessAtCreateIsolateUnboundedMaxHeapMemory()714 public void testSuccessAtCreateIsolateUnboundedMaxHeapMemory() throws Exception { 715 when(mMockedSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) 716 .thenReturn(true); 717 IsolateSettings enforcedHeapIsolateSettings = 718 IsolateSettings.builder() 719 .setEnforceMaxHeapSizeFeature(true) 720 .setMaxHeapSizeBytes(0) 721 .setIsolateConsoleMessageInLogsEnabled(mDefaultIsolateConsoleMessageInLogs) 722 .build(); 723 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 724 725 when(mMockedIsolate.evaluateJavaScriptAsync(anyString())) 726 .thenReturn(Futures.immediateFuture("\"hello world\"")); 727 728 assertThat( 729 callJSEngine( 730 JSScriptEngine.createNewInstanceForTesting( 731 ApplicationProvider.getApplicationContext(), 732 mMockSandboxProvider, 733 sMockProfiler, 734 sLogger), 735 "function test() { return \"hello world\"; }", 736 ImmutableList.of(), 737 "test", 738 enforcedHeapIsolateSettings, 739 mNoOpRetryStrategy)) 740 .isEqualTo("\"hello world\""); 741 742 verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME); 743 verify(mMockedSandbox) 744 .isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE); 745 } 746 747 @Test testSuccessAtCreateIsolateBoundedMaxHeapMemory()748 public void testSuccessAtCreateIsolateBoundedMaxHeapMemory() throws Exception { 749 when(mMockedSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) 750 .thenReturn(true); 751 IsolateSettings enforcedHeapIsolateSettings = 752 IsolateSettings.builder() 753 .setEnforceMaxHeapSizeFeature(true) 754 .setMaxHeapSizeBytes(1000) 755 .setIsolateConsoleMessageInLogsEnabled(mDefaultIsolateConsoleMessageInLogs) 756 .build(); 757 when(mMockedSandbox.createIsolate(Mockito.any(IsolateStartupParameters.class))) 758 .thenReturn(mMockedIsolate); 759 760 when(mMockedIsolate.evaluateJavaScriptAsync(anyString())) 761 .thenReturn(Futures.immediateFuture("\"hello world\"")); 762 763 assertThat( 764 callJSEngine( 765 JSScriptEngine.createNewInstanceForTesting( 766 ApplicationProvider.getApplicationContext(), 767 mMockSandboxProvider, 768 sMockProfiler, 769 sLogger), 770 "function test() { return \"hello world\"; }", 771 ImmutableList.of(), 772 "test", 773 enforcedHeapIsolateSettings, 774 mNoOpRetryStrategy)) 775 .isEqualTo("\"hello world\""); 776 777 verify(sMockProfiler).start(JSScriptEngineLogConstants.ISOLATE_CREATE_TIME); 778 verify(mMockedSandbox) 779 .isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE); 780 } 781 782 @Test testConsoleMessageCallbackSuccess()783 public void testConsoleMessageCallbackSuccess() throws Exception { 784 IsolateSettings isolateSettings = IsolateSettings.forMaxHeapSizeEnforcementDisabled(true); 785 when(mMockedSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING)) 786 .thenReturn(true); 787 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 788 when(mMockedIsolate.evaluateJavaScriptAsync(anyString())) 789 .thenReturn(Futures.immediateFuture("\"hello world\"")); 790 791 callJSEngine( 792 JSScriptEngine.createNewInstanceForTesting( 793 ApplicationProvider.getApplicationContext(), 794 mMockSandboxProvider, 795 sMockProfiler, 796 sLogger), 797 "function test() { return \"hello world\"; }", 798 ImmutableList.of(), 799 "test", 800 isolateSettings, 801 mNoOpRetryStrategy); 802 803 verify(mMockedSandbox).isFeatureSupported(JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING); 804 verify(mMockedIsolate) 805 .setConsoleCallback( 806 any(ExecutorService.class), any(JavaScriptConsoleCallback.class)); 807 } 808 809 @Test testConsoleMessageCallbackIsNotAddedWhenDisabled()810 public void testConsoleMessageCallbackIsNotAddedWhenDisabled() throws Exception { 811 IsolateSettings isolateSettings = IsolateSettings.forMaxHeapSizeEnforcementDisabled(false); 812 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 813 when(mMockedIsolate.evaluateJavaScriptAsync(anyString())) 814 .thenReturn(Futures.immediateFuture("\"hello world\"")); 815 816 callJSEngine( 817 JSScriptEngine.createNewInstanceForTesting( 818 ApplicationProvider.getApplicationContext(), 819 mMockSandboxProvider, 820 sMockProfiler, 821 sLogger), 822 "function test() { return \"hello world\"; }", 823 ImmutableList.of(), 824 "test", 825 isolateSettings, 826 mNoOpRetryStrategy); 827 828 verify(mMockedSandbox, never()) 829 .isFeatureSupported(JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING); 830 verify(mMockedIsolate, never()) 831 .setConsoleCallback( 832 any(ExecutorService.class), any(JavaScriptConsoleCallback.class)); 833 } 834 835 @Test testConsoleMessageCallbackIsNotSetIfFeatureNotAvailable()836 public void testConsoleMessageCallbackIsNotSetIfFeatureNotAvailable() throws Exception { 837 IsolateSettings isolateSettings = IsolateSettings.forMaxHeapSizeEnforcementDisabled(true); 838 when(mMockedSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING)) 839 .thenReturn(false); 840 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 841 when(mMockedIsolate.evaluateJavaScriptAsync(anyString())) 842 .thenReturn(Futures.immediateFuture("\"hello world\"")); 843 844 callJSEngine( 845 JSScriptEngine.createNewInstanceForTesting( 846 ApplicationProvider.getApplicationContext(), 847 mMockSandboxProvider, 848 sMockProfiler, 849 sLogger), 850 "function test() { return \"hello world\"; }", 851 ImmutableList.of(), 852 "test", 853 isolateSettings, 854 mNoOpRetryStrategy); 855 856 verify(mMockedSandbox).isFeatureSupported(JavaScriptSandbox.JS_FEATURE_CONSOLE_MESSAGING); 857 verify(mMockedIsolate, never()) 858 .setConsoleCallback( 859 any(ExecutorService.class), any(JavaScriptConsoleCallback.class)); 860 } 861 862 // Troubles between google-java-format and checkstyle 863 // CHECKSTYLE:OFF IndentationCheck 864 @Test testIsolateIsClosedWhenEvaluationCompletes()865 public void testIsolateIsClosedWhenEvaluationCompletes() throws Exception { 866 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 867 when(mMockedIsolate.evaluateJavaScriptAsync(anyString())) 868 .thenReturn(Futures.immediateFuture("hello world")); 869 870 AtomicBoolean isolateHasBeenClosed = new AtomicBoolean(false); 871 CountDownLatch isolateIsClosedLatch = new CountDownLatch(1); 872 doAnswer( 873 invocation -> { 874 isolateHasBeenClosed.set(true); 875 isolateIsClosedLatch.countDown(); 876 return null; 877 }) 878 .when(mMockedIsolate) 879 .close(); 880 881 callJSEngine( 882 JSScriptEngine.createNewInstanceForTesting( 883 ApplicationProvider.getApplicationContext(), 884 mMockSandboxProvider, 885 sMockProfiler, 886 sLogger), 887 "function test() { return \"hello world\"; }", 888 ImmutableList.of(), 889 "test", 890 mDefaultIsolateSettings, 891 mNoOpRetryStrategy); 892 893 isolateIsClosedLatch.await(1, TimeUnit.SECONDS); 894 // Using Mockito.verify made the test unstable (mockito call registration was in a 895 // race condition with the verify call) 896 assertTrue(isolateHasBeenClosed.get()); 897 } 898 899 @Test testIsolateIsClosedWhenEvaluationFails()900 public void testIsolateIsClosedWhenEvaluationFails() throws Exception { 901 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 902 when(mMockedIsolate.evaluateJavaScriptAsync(anyString())) 903 .thenReturn( 904 Futures.immediateFailedFuture(new RuntimeException("JS execution failed"))); 905 906 AtomicBoolean isolateHasBeenClosed = new AtomicBoolean(false); 907 CountDownLatch isolateIsClosedLatch = new CountDownLatch(1); 908 doAnswer( 909 invocation -> { 910 isolateHasBeenClosed.set(true); 911 isolateIsClosedLatch.countDown(); 912 return null; 913 }) 914 .when(mMockedIsolate) 915 .close(); 916 917 assertThrows( 918 ExecutionException.class, 919 () -> 920 callJSEngine( 921 JSScriptEngine.createNewInstanceForTesting( 922 ApplicationProvider.getApplicationContext(), 923 mMockSandboxProvider, 924 sMockProfiler, 925 sLogger), 926 "function test() { return \"hello world\"; }", 927 ImmutableList.of(), 928 "test", 929 mDefaultIsolateSettings, 930 mNoOpRetryStrategy)); 931 932 isolateIsClosedLatch.await(1, TimeUnit.SECONDS); 933 // Using Mockito.verify made the test unstable (mockito call registration was in a 934 // race condition with the verify call) 935 assertTrue(isolateHasBeenClosed.get()); 936 } 937 938 @Test testIsolateIsClosedWhenEvaluationIsCancelled()939 public void testIsolateIsClosedWhenEvaluationIsCancelled() throws Exception { 940 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 941 942 CountDownLatch jsEvaluationStartedLatch = new CountDownLatch(1); 943 CountDownLatch stallJsEvaluationLatch = new CountDownLatch(1); 944 ListeningExecutorService callbackExecutor = 945 MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); 946 doAnswer( 947 invocation -> { 948 jsEvaluationStartedLatch.countDown(); 949 sLogger.i("JS execution started"); 950 return callbackExecutor.submit( 951 () -> { 952 try { 953 stallJsEvaluationLatch.await(); 954 } catch (InterruptedException ignored) { 955 Thread.currentThread().interrupt(); 956 } 957 sLogger.i("JS execution completed,"); 958 return "hello world"; 959 }); 960 }) 961 .when(mMockedIsolate) 962 .evaluateJavaScriptAsync(anyString()); 963 964 JSScriptEngine engine = 965 JSScriptEngine.createNewInstanceForTesting( 966 sContext, mMockSandboxProvider, sMockProfiler, sLogger); 967 ListenableFuture<String> jsExecutionFuture = 968 engine.evaluate( 969 "function test() { return \"hello world\"; }", 970 ImmutableList.of(), 971 "test", 972 mDefaultIsolateSettings, 973 mNoOpRetryStrategy); 974 975 // Cancelling only after the processing started and the sandbox has been created 976 jsEvaluationStartedLatch.await(1, TimeUnit.SECONDS); 977 // Explicitly verifying that isolate was created as latch could have just counted down 978 verify(mMockedSandbox).createIsolate(); 979 assertTrue( 980 "Execution for the future should have been still ongoing when cancelled", 981 jsExecutionFuture.cancel(true)); 982 verify(mMockedIsolate, timeout(2000).atLeast(1)).close(); 983 } 984 985 @Test testIsolateIsClosedWhenEvaluationTimesOut()986 public void testIsolateIsClosedWhenEvaluationTimesOut() throws Exception { 987 when(mMockedSandbox.createIsolate()).thenReturn(mMockedIsolate); 988 CountDownLatch jsEvaluationStartedLatch = new CountDownLatch(1); 989 CountDownLatch stallJsEvaluationLatch = new CountDownLatch(1); 990 ListeningExecutorService callbackExecutor = 991 MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); 992 doAnswer( 993 invocation -> { 994 jsEvaluationStartedLatch.countDown(); 995 sLogger.i("JS execution started"); 996 return callbackExecutor.submit( 997 () -> { 998 try { 999 stallJsEvaluationLatch.await(); 1000 } catch (InterruptedException ignored) { 1001 Thread.currentThread().interrupt(); 1002 } 1003 sLogger.i("JS execution completed"); 1004 return "hello world"; 1005 }); 1006 }) 1007 .when(mMockedIsolate) 1008 .evaluateJavaScriptAsync(anyString()); 1009 1010 JSScriptEngine engine = 1011 JSScriptEngine.createNewInstanceForTesting( 1012 ApplicationProvider.getApplicationContext(), 1013 mMockSandboxProvider, 1014 sMockProfiler, 1015 sLogger); 1016 ExecutionException timeoutException = 1017 assertThrows( 1018 ExecutionException.class, 1019 () -> 1020 FluentFuture.from( 1021 engine.evaluate( 1022 "function test() { return \"hello world\";" 1023 + " }", 1024 ImmutableList.of(), 1025 "test", 1026 mDefaultIsolateSettings, 1027 mNoOpRetryStrategy)) 1028 .withTimeout( 1029 500, 1030 TimeUnit.MILLISECONDS, 1031 new ScheduledThreadPoolExecutor(1)) 1032 .get()); 1033 1034 jsEvaluationStartedLatch.await(1, TimeUnit.SECONDS); 1035 // Explicitly verifying that isolate was created as latch could have just counted down 1036 verify(mMockedSandbox).createIsolate(); 1037 // Verifying close was invoked 1038 verify(mMockedIsolate, timeout(2000).atLeast(1)).close(); 1039 assertThat(timeoutException).hasCauseThat().isInstanceOf(TimeoutException.class); 1040 } 1041 // CHECKSTYLE:ON IndentationCheck 1042 1043 @Test testThrowsExceptionAndRecreateSandboxIfIsolateCreationFails()1044 public void testThrowsExceptionAndRecreateSandboxIfIsolateCreationFails() throws Exception { 1045 doThrow(new RuntimeException("Simulating isolate creation failure")) 1046 .when(mMockedSandbox) 1047 .createIsolate(); 1048 1049 JSScriptEngine engine = 1050 JSScriptEngine.createNewInstanceForTesting( 1051 ApplicationProvider.getApplicationContext(), 1052 mMockSandboxProvider, 1053 sMockProfiler, 1054 sLogger); 1055 1056 assertThrows( 1057 ExecutionException.class, 1058 () -> 1059 callJSEngine( 1060 engine, 1061 "function test() { return \"hello world\";" + " }", 1062 ImmutableList.of(), 1063 "test", 1064 mDefaultIsolateSettings, 1065 mNoOpRetryStrategy)); 1066 verify(mMockSandboxProvider).destroyIfCurrentInstance(mMockedSandbox); 1067 } 1068 1069 @Test testCanUseWasmModuleInScript()1070 public void testCanUseWasmModuleInScript() throws Exception { 1071 assumeTrue(sJSScriptEngine.isWasmSupported().get(4, TimeUnit.SECONDS)); 1072 1073 String jsUsingWasmModule = 1074 "\"use strict\";\n" 1075 + "\n" 1076 + "function callWasm(input, wasmModule) {\n" 1077 + " const instance = new WebAssembly.Instance(wasmModule);\n" 1078 + "\n" 1079 + " return instance.exports._fact(input);\n" 1080 + "\n" 1081 + "}"; 1082 1083 String result = 1084 callJSEngine( 1085 jsUsingWasmModule, 1086 readBinaryAsset(WASM_MODULE), 1087 ImmutableList.of(numericArg("input", 3)), 1088 "callWasm", 1089 mDefaultIsolateSettings, 1090 mNoOpRetryStrategy); 1091 1092 assertThat(result).isEqualTo("6"); 1093 } 1094 1095 @Test testCanNotUseWasmModuleInScriptIfWebViewDoesNotSupportWasm()1096 public void testCanNotUseWasmModuleInScriptIfWebViewDoesNotSupportWasm() throws Exception { 1097 assumeFalse(sJSScriptEngine.isWasmSupported().get(4, TimeUnit.SECONDS)); 1098 1099 String jsUsingWasmModule = 1100 "\"use strict\";\n" 1101 + "\n" 1102 + "function callWasm(input, wasmModule) {\n" 1103 + " const instance = new WebAssembly.Instance(wasmModule);\n" 1104 + "\n" 1105 + " return instance.exports._fact(input);\n" 1106 + "\n" 1107 + "}"; 1108 1109 ExecutionException outer = 1110 assertThrows( 1111 ExecutionException.class, 1112 () -> 1113 callJSEngine( 1114 jsUsingWasmModule, 1115 readBinaryAsset(WASM_MODULE), 1116 ImmutableList.of(numericArg("input", 3)), 1117 "callWasm", 1118 mDefaultIsolateSettings, 1119 mNoOpRetryStrategy)); 1120 1121 assertThat(outer).hasCauseThat().isInstanceOf(IllegalStateException.class); 1122 } 1123 callJSEngineAndAssertExecutionException( JSScriptEngine engine, IsolateSettings isolateSettings)1124 private ExecutionException callJSEngineAndAssertExecutionException( 1125 JSScriptEngine engine, IsolateSettings isolateSettings) { 1126 return assertThrows( 1127 ExecutionException.class, 1128 () -> 1129 callJSEngine( 1130 engine, 1131 "function test() { return \"hello world\"; }", 1132 ImmutableList.of(), 1133 "test", 1134 isolateSettings, 1135 mNoOpRetryStrategy)); 1136 } 1137 callJSEngine( @onNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull IsolateSettings isolateSettings, @NonNull RetryStrategy retryStrategy)1138 private String callJSEngine( 1139 @NonNull String jsScript, 1140 @NonNull List<JSScriptArgument> args, 1141 @NonNull String functionName, 1142 @NonNull IsolateSettings isolateSettings, 1143 @NonNull RetryStrategy retryStrategy) 1144 throws Exception { 1145 return callJSEngine( 1146 sJSScriptEngine, jsScript, args, functionName, isolateSettings, retryStrategy); 1147 } 1148 callJSEngine( @onNull String jsScript, @NonNull byte[] wasmBytes, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull IsolateSettings isolateSettings, @NonNull RetryStrategy retryStrategy)1149 private String callJSEngine( 1150 @NonNull String jsScript, 1151 @NonNull byte[] wasmBytes, 1152 @NonNull List<JSScriptArgument> args, 1153 @NonNull String functionName, 1154 @NonNull IsolateSettings isolateSettings, 1155 @NonNull RetryStrategy retryStrategy) 1156 throws Exception { 1157 return callJSEngine( 1158 sJSScriptEngine, 1159 jsScript, 1160 wasmBytes, 1161 args, 1162 functionName, 1163 isolateSettings, 1164 retryStrategy); 1165 } 1166 callJSEngine( @onNull JSScriptEngine jsScriptEngine, @NonNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull IsolateSettings isolateSettings, @NonNull RetryStrategy retryStrategy)1167 private String callJSEngine( 1168 @NonNull JSScriptEngine jsScriptEngine, 1169 @NonNull String jsScript, 1170 @NonNull List<JSScriptArgument> args, 1171 @NonNull String functionName, 1172 @NonNull IsolateSettings isolateSettings, 1173 @NonNull RetryStrategy retryStrategy) 1174 throws Exception { 1175 CountDownLatch resultLatch = new CountDownLatch(1); 1176 ListenableFuture<String> futureResult = 1177 callJSEngineAsync( 1178 jsScriptEngine, 1179 jsScript, 1180 args, 1181 functionName, 1182 resultLatch, 1183 isolateSettings, 1184 retryStrategy); 1185 resultLatch.await(); 1186 return futureResult.get(); 1187 } 1188 callJSEngine( @onNull JSScriptEngine jsScriptEngine, @NonNull String jsScript, @NonNull byte[] wasmBytes, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull IsolateSettings isolateSettings, @NonNull RetryStrategy retryStrategy)1189 private String callJSEngine( 1190 @NonNull JSScriptEngine jsScriptEngine, 1191 @NonNull String jsScript, 1192 @NonNull byte[] wasmBytes, 1193 @NonNull List<JSScriptArgument> args, 1194 @NonNull String functionName, 1195 @NonNull IsolateSettings isolateSettings, 1196 @NonNull RetryStrategy retryStrategy) 1197 throws Exception { 1198 CountDownLatch resultLatch = new CountDownLatch(1); 1199 ListenableFuture<String> futureResult = 1200 callJSEngineAsync( 1201 jsScriptEngine, 1202 jsScript, 1203 wasmBytes, 1204 args, 1205 functionName, 1206 resultLatch, 1207 isolateSettings, 1208 retryStrategy); 1209 resultLatch.await(); 1210 return futureResult.get(); 1211 } 1212 callJSEngineAsync( @onNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull CountDownLatch resultLatch, @NonNull IsolateSettings isolateSettings, @NonNull RetryStrategy retryStrategy)1213 private ListenableFuture<String> callJSEngineAsync( 1214 @NonNull String jsScript, 1215 @NonNull List<JSScriptArgument> args, 1216 @NonNull String functionName, 1217 @NonNull CountDownLatch resultLatch, 1218 @NonNull IsolateSettings isolateSettings, 1219 @NonNull RetryStrategy retryStrategy) { 1220 return callJSEngineAsync( 1221 sJSScriptEngine, 1222 jsScript, 1223 args, 1224 functionName, 1225 resultLatch, 1226 isolateSettings, 1227 retryStrategy); 1228 } 1229 callJSEngineAsync( @onNull JSScriptEngine engine, @NonNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull CountDownLatch resultLatch, @NonNull IsolateSettings isolateSettings, @NonNull RetryStrategy retryStrategy)1230 private ListenableFuture<String> callJSEngineAsync( 1231 @NonNull JSScriptEngine engine, 1232 @NonNull String jsScript, 1233 @NonNull List<JSScriptArgument> args, 1234 @NonNull String functionName, 1235 @NonNull CountDownLatch resultLatch, 1236 @NonNull IsolateSettings isolateSettings, 1237 @NonNull RetryStrategy retryStrategy) { 1238 Objects.requireNonNull(engine); 1239 Objects.requireNonNull(resultLatch); 1240 sLogger.v("Calling JavaScriptSandbox"); 1241 ListenableFuture<String> result = 1242 engine.evaluate(jsScript, args, functionName, isolateSettings, retryStrategy); 1243 result.addListener(resultLatch::countDown, mExecutorService); 1244 return result; 1245 } 1246 callJSEngineAsync( @onNull JSScriptEngine engine, @NonNull String jsScript, @NonNull byte[] wasmBytes, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull CountDownLatch resultLatch, @NonNull IsolateSettings isolateSettings, @NonNull RetryStrategy retryStrategy)1247 private ListenableFuture<String> callJSEngineAsync( 1248 @NonNull JSScriptEngine engine, 1249 @NonNull String jsScript, 1250 @NonNull byte[] wasmBytes, 1251 @NonNull List<JSScriptArgument> args, 1252 @NonNull String functionName, 1253 @NonNull CountDownLatch resultLatch, 1254 @NonNull IsolateSettings isolateSettings, 1255 @NonNull RetryStrategy retryStrategy) { 1256 Objects.requireNonNull(engine); 1257 Objects.requireNonNull(resultLatch); 1258 sLogger.v("Calling JavaScriptSandbox"); 1259 ListenableFuture<String> result = 1260 engine.evaluate( 1261 jsScript, wasmBytes, args, functionName, isolateSettings, retryStrategy); 1262 result.addListener(resultLatch::countDown, mExecutorService); 1263 return result; 1264 } 1265 readBinaryAsset(@onNull String assetName)1266 private byte[] readBinaryAsset(@NonNull String assetName) throws IOException { 1267 InputStream inputStream = sContext.getAssets().open(assetName); 1268 return SdkLevel.isAtLeastT() 1269 ? inputStream.readAllBytes() 1270 : ByteStreams.toByteArray(inputStream); 1271 } 1272 } 1273