Skip to content

Commit

Permalink
Write instrumentation tests for Webview JS Sandbox
Browse files Browse the repository at this point in the history
These tests are similar to UI tests such that they compile and install
a local version of WebView and set it as provider before beginning the
tests. The test uses actual IPC calls to the webview provider package
and is a more complete integration test as compared to
webview_instrumentation_test_apk test.

Bug: 1309447
Change-Id: I0b6fb009e48b07ca6615052ee20fa69f9971e1d0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3550196
Reviewed-by: Richard Coles <torne@chromium.org>
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Commit-Queue: Abhijith Nair <abhijithnair@chromium.org>
Cr-Commit-Position: refs/heads/main@{#986120}
  • Loading branch information
Abhijith Nair authored and Chromium LUCI CQ committed Mar 28, 2022
1 parent 04c0dad commit dd793b1
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 11 deletions.
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ group("gn_all") {

if (is_android) {
deps += [
"//android_webview/tools/js_sandbox_tests:webview_js_sandbox_test_app",
"//base:base_junit_tests",
"//base/android/jni_generator:jni_generator_tests",
"//base/android/linker:chromium_android_linker",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.webkit.WebView;

import androidx.annotation.VisibleForTesting;

import org.chromium.android_webview.js.common.IJsSandboxContext;
import org.chromium.android_webview.js.common.IJsSandboxService;
import org.chromium.android_webview.js.renderer.JsSandboxService0;
import org.chromium.base.ContextUtils;

/**
Expand All @@ -26,6 +29,8 @@ public class AwJsSandbox implements AutoCloseable {
// 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 TAG = "AwJsSandbox";
private static final String JS_SANDBOX_SERVICE_NAME =
"org.chromium.android_webview.js.renderer.JsSandboxService0";

private IJsSandboxService mJsSandboxService;
private ConnectionSetup mConnection;
Expand Down Expand Up @@ -65,13 +70,31 @@ public interface ReadyCallback {
* @param callback used to pass a callback function on creation of object.
*/
public static void newConnectedInstance(ReadyCallback callback) {
Intent intent = new Intent(ContextUtils.getApplicationContext(), JsSandboxService0.class);
PackageInfo systemWebViewPackage = WebView.getCurrentWebViewPackage();
ComponentName compName =
new ComponentName(systemWebViewPackage.packageName, JS_SANDBOX_SERVICE_NAME);
int flag = Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE;
bindToServiceWithCallback(compName, flag, callback);
}

@VisibleForTesting
public static void newConnectedInstanceForTesting(ReadyCallback callback) {
ComponentName compName =
new ComponentName(ContextUtils.getApplicationContext(), JS_SANDBOX_SERVICE_NAME);
int flag = Context.BIND_AUTO_CREATE;
bindToServiceWithCallback(compName, flag, callback);
}

private static void bindToServiceWithCallback(
ComponentName compName, int flag, ReadyCallback callback) {
Intent intent = new Intent();
intent.setComponent(compName);
ConnectionSetup connectionSetup = new ConnectionSetup(callback);
boolean isBinding = ContextUtils.getApplicationContext().bindService(
intent, connectionSetup, Context.BIND_AUTO_CREATE);
boolean isBinding =
ContextUtils.getApplicationContext().bindService(intent, connectionSetup, flag);
if (!isBinding) {
throw new RuntimeException(
"System couldn't find the sandbox service or client doesn't have"
"System couldn't find the sandbox service or client doesn't have "
+ "permission to bind to it " + intent);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void testSimpleJsEvaluation() throws Throwable {
final String expected = "PASS";
TestExecutionCallback callback = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsSandbox.newConnectedInstanceForTesting((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext = jsSandbox.createContext();
jsContext.evaluateJavascript(code, callback);
});
Expand All @@ -59,7 +59,7 @@ public void testClosingOneContext() throws Throwable {
final String expected = "PASS";
TestExecutionCallback callback = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsSandbox.newConnectedInstanceForTesting((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
AwJsContext jsContext2 = jsSandbox.createContext();
jsContext1.close();
Expand All @@ -81,7 +81,7 @@ public void testEvaluationInTwoContexts() throws Throwable {
TestExecutionCallback callback1 = new TestExecutionCallback();
TestExecutionCallback callback2 = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsSandbox.newConnectedInstanceForTesting((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
jsContext1.evaluateJavascript(code1, callback1);
AwJsContext jsContext2 = jsSandbox.createContext();
Expand All @@ -106,7 +106,7 @@ public void testTwoContextsDoNotShareEnvironment() throws Throwable {
TestExecutionCallback callback1 = new TestExecutionCallback();
TestExecutionCallback callback2 = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsSandbox.newConnectedInstanceForTesting((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
jsContext1.evaluateJavascript(code1, callback1);
AwJsContext jsContext2 = jsSandbox.createContext();
Expand All @@ -131,7 +131,7 @@ public void testTwoExecutionsShareEnvironment() throws Throwable {
TestExecutionCallback callback1 = new TestExecutionCallback();
TestExecutionCallback callback2 = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsSandbox.newConnectedInstanceForTesting((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
jsContext1.evaluateJavascript(code1, callback1);
jsContext1.evaluateJavascript(code2, callback2);
Expand All @@ -152,7 +152,7 @@ public void testJsEvaluationError() throws Throwable {
final String contains = "SyntaxError";
TestExecutionCallback callback = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance(jsSandbox -> {
AwJsSandbox.newConnectedInstanceForTesting(jsSandbox -> {
AwJsContext jsContext = jsSandbox.createContext();
jsContext.evaluateJavascript(code, callback);
});
Expand Down
34 changes: 34 additions & 0 deletions android_webview/tools/js_sandbox_tests/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2022 The Chromium Authors.All rights reserved.
# Use of this source code is governed by a BSD - style license that can be
# found in the LICENSE file.

import("//build/config/android/rules.gni")
import("//testing/test.gni")

group("webview_js_sandbox_test_app") {
testonly = true
deps = [
":webview_js_sandbox_test_app_apk",
":webview_js_sandbox_test_app_test_apk",
]
}

android_apk("webview_js_sandbox_test_app_apk") {
apk_name = "WebViewJsSandboxTestApp"
android_manifest = "java/AndroidManifest.xml"
deps = []
}

instrumentation_test_apk("webview_js_sandbox_test_app_test_apk") {
apk_name = "WebViewJsSandboxTestAppTest"
apk_under_test = ":webview_js_sandbox_test_app_apk"
android_manifest = "javatests/AndroidManifest.xml"
sources = [ "javatests/src/org/chromium/webview_js_sandbox_test/test/WebViewJsSandboxTest.java" ]
deps = [
"//android_webview:js_sandbox_java",
"//base:base_java_test_support",
"//third_party/androidx:androidx_test_runner_java",
"//third_party/junit:junit",
]
use_webview_provider = system_webview_apk_target
}
18 changes: 18 additions & 0 deletions android_webview/tools/js_sandbox_tests/java/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!--
* Copyright 2022 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
-->

<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.chromium.webview_js_sandbox_test"
android:versionCode="1"
android:versionName="1.0" >

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--
* Copyright 2022 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
-->

<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.chromium.webview_js_sandbox_test.test">

<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />

<!-- We add an application tag here just so that we can indicate that this
package needs to link against the android.test library, which is
needed when building test cases. -->
<application>
<uses-library android:name="android.test.runner" />
</application>

<instrumentation android:name="org.chromium.base.test.BaseChromiumAndroidJUnitRunner"
android:targetPackage="org.chromium.webview_js_sandbox_test"
android:label="Tests for org.chromium.webview_js_sandbox_test"/>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.webview_js_sandbox_test.test;

import androidx.test.filters.MediumTest;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.android_webview.js.browser.AwJsContext;
import org.chromium.android_webview.js.browser.AwJsSandbox;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;

/** Instrumentation test for JsSandboxService. */
@RunWith(BaseJUnit4ClassRunner.class)
public class WebViewJsSandboxTest {
private class TestExecutionCallback implements AwJsContext.ExecutionCallback {
public CallbackHelper helper = new CallbackHelper();
public String result;
public String error;

@Override
public void reportResult(String result) {
this.result = result;
helper.notifyCalled();
}

@Override
public void reportError(String error) {
this.error = error;
helper.notifyCalled();
}
}

@Test
@MediumTest
public void testSimpleJsEvaluation() throws Throwable {
final String code = "'PASS'";
final String expected = "PASS";
TestExecutionCallback callback = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext = jsSandbox.createContext();
jsContext.evaluateJavascript(code, callback);
});

callback.helper.waitForCallback("Timed out waiting for reportResult() to be called", 0);
Assert.assertEquals(expected, callback.result);
}

@Test
@MediumTest
public void testClosingOneContext() throws Throwable {
final String code = "'PASS'";
final String expected = "PASS";
TestExecutionCallback callback = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
AwJsContext jsContext2 = jsSandbox.createContext();
jsContext1.close();
jsContext2.evaluateJavascript(code, callback);
jsContext2.close();
});

callback.helper.waitForCallback("Timed out waiting for reportResult() to be called", 0);
Assert.assertEquals(expected, callback.result);
}

@Test
@MediumTest
public void testEvaluationInTwoContexts() throws Throwable {
final String code1 = "this.x = 'PASS';\n";
final String expected1 = "PASS";
final String code2 = "this.x = 'SUPER_PASS';\n";
final String expected2 = "SUPER_PASS";
TestExecutionCallback callback1 = new TestExecutionCallback();
TestExecutionCallback callback2 = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
jsContext1.evaluateJavascript(code1, callback1);
AwJsContext jsContext2 = jsSandbox.createContext();
jsContext2.evaluateJavascript(code2, callback2);
});
callback1.helper.waitForCallback(
"Timed out waiting for reportResult() to be called for first case", 0);
callback2.helper.waitForCallback(
"Timed out waiting for reportResult() to be called for second case", 0);

Assert.assertEquals(expected1, callback1.result);
Assert.assertEquals(expected2, callback2.result);
}

@Test
@MediumTest
public void testTwoContextsDoNotShareEnvironment() throws Throwable {
final String code1 = "this.y = 'PASS';\n";
final String expected1 = "PASS";
final String code2 = "this.y = this.y + ' PASS';\n";
final String expected2 = "undefined PASS";
TestExecutionCallback callback1 = new TestExecutionCallback();
TestExecutionCallback callback2 = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
jsContext1.evaluateJavascript(code1, callback1);
AwJsContext jsContext2 = jsSandbox.createContext();
jsContext2.evaluateJavascript(code2, callback2);
});
callback1.helper.waitForCallback(
"Timed out waiting for reportResult() to be called for first case", 0);
callback2.helper.waitForCallback(
"Timed out waiting for reportResult() to be called for second case", 0);

Assert.assertEquals(expected1, callback1.result);
Assert.assertEquals(expected2, callback2.result);
}

@Test
@MediumTest
public void testTwoExecutionsShareEnvironment() throws Throwable {
final String code1 = "this.z = 'PASS';\n";
final String expected1 = "PASS";
final String code2 = "this.z = this.z + ' PASS';\n";
final String expected2 = "PASS PASS";
TestExecutionCallback callback1 = new TestExecutionCallback();
TestExecutionCallback callback2 = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance((AwJsSandbox jsSandbox) -> {
AwJsContext jsContext1 = jsSandbox.createContext();
jsContext1.evaluateJavascript(code1, callback1);
jsContext1.evaluateJavascript(code2, callback2);
});
callback1.helper.waitForCallback(
"Timed out waiting for reportResult() to be called for first case", 0);
callback2.helper.waitForCallback(
"Timed out waiting for reportResult() to be called for second case", 0);

Assert.assertEquals(expected1, callback1.result);
Assert.assertEquals(expected2, callback2.result);
}

@Test
@MediumTest
public void testJsEvaluationError() throws Throwable {
final String code = ".";
final String contains = "SyntaxError";
TestExecutionCallback callback = new TestExecutionCallback();

AwJsSandbox.newConnectedInstance(jsSandbox -> {
AwJsContext jsContext = jsSandbox.createContext();
jsContext.evaluateJavascript(code, callback);
});

callback.helper.waitForCallback("Timed out waiting for reportError() to be called", 0);
Assert.assertTrue(callback.error.contains(contains));
}
}

0 comments on commit dd793b1

Please sign in to comment.