If a method called on a Java object through the Java Bridge throws an uncaught exception, we throw a JavaScript exception. See WebKit change https://android-git.corp.google.com/g/184252 Bug: 6386557 Change-Id: I35c456c162fd9b2a078ee9fce1ea68404b4c829c
443 lines
17 KiB
Java
443 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2011 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.
|
|
*/
|
|
|
|
/**
|
|
* Part of the test suite for the WebView's Java Bridge. Tests a number of features including ...
|
|
* - The type of injected objects
|
|
* - The type of their methods
|
|
* - Replacing objects
|
|
* - Removing objects
|
|
* - Access control
|
|
* - Calling methods on returned objects
|
|
* - Multiply injected objects
|
|
* - Threading
|
|
* - Inheritance
|
|
*
|
|
* To run this test ...
|
|
* adb shell am instrument -w -e class com.android.webviewtests.JavaBridgeBasicsTest \
|
|
* com.android.webviewtests/android.test.InstrumentationTestRunner
|
|
*/
|
|
|
|
package com.android.webviewtests;
|
|
|
|
public class JavaBridgeBasicsTest extends JavaBridgeTestBase {
|
|
private class TestController extends Controller {
|
|
private int mIntValue;
|
|
private long mLongValue;
|
|
private String mStringValue;
|
|
private boolean mBooleanValue;
|
|
|
|
public synchronized void setIntValue(int x) {
|
|
mIntValue = x;
|
|
notifyResultIsReady();
|
|
}
|
|
public synchronized void setLongValue(long x) {
|
|
mLongValue = x;
|
|
notifyResultIsReady();
|
|
}
|
|
public synchronized void setStringValue(String x) {
|
|
mStringValue = x;
|
|
notifyResultIsReady();
|
|
}
|
|
public synchronized void setBooleanValue(boolean x) {
|
|
mBooleanValue = x;
|
|
notifyResultIsReady();
|
|
}
|
|
|
|
public synchronized int waitForIntValue() {
|
|
waitForResult();
|
|
return mIntValue;
|
|
}
|
|
public synchronized long waitForLongValue() {
|
|
waitForResult();
|
|
return mLongValue;
|
|
}
|
|
public synchronized String waitForStringValue() {
|
|
waitForResult();
|
|
return mStringValue;
|
|
}
|
|
public synchronized boolean waitForBooleanValue() {
|
|
waitForResult();
|
|
return mBooleanValue;
|
|
}
|
|
}
|
|
|
|
private static class ObjectWithStaticMethod {
|
|
public static String staticMethod() {
|
|
return "foo";
|
|
}
|
|
}
|
|
|
|
TestController mTestController;
|
|
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
mTestController = new TestController();
|
|
setUpWebView(mTestController, "testController");
|
|
}
|
|
|
|
// Note that this requires that we can pass a JavaScript string to Java.
|
|
protected String executeJavaScriptAndGetStringResult(String script) throws Throwable {
|
|
executeJavaScript("testController.setStringValue(" + script + ");");
|
|
return mTestController.waitForStringValue();
|
|
}
|
|
|
|
protected void injectObjectAndReload(final Object object, final String name) throws Throwable {
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().addJavascriptInterface(object, name);
|
|
getWebView().reload();
|
|
}
|
|
});
|
|
mWebViewClient.waitForOnPageFinished();
|
|
}
|
|
|
|
// Note that this requires that we can pass a JavaScript boolean to Java.
|
|
private void assertRaisesException(String script) throws Throwable {
|
|
executeJavaScript("try {" +
|
|
script + ";" +
|
|
" testController.setBooleanValue(false);" +
|
|
"} catch (exception) {" +
|
|
" testController.setBooleanValue(true);" +
|
|
"}");
|
|
assertTrue(mTestController.waitForBooleanValue());
|
|
}
|
|
|
|
public void testTypeOfInjectedObject() throws Throwable {
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
|
|
}
|
|
|
|
public void testAdditionNotReflectedUntilReload() throws Throwable {
|
|
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().addJavascriptInterface(new Object(), "testObject");
|
|
}
|
|
});
|
|
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().reload();
|
|
}
|
|
});
|
|
mWebViewClient.waitForOnPageFinished();
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
}
|
|
|
|
public void testRemovalNotReflectedUntilReload() throws Throwable {
|
|
injectObjectAndReload(new Object(), "testObject");
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().removeJavascriptInterface("testObject");
|
|
}
|
|
});
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().reload();
|
|
}
|
|
});
|
|
mWebViewClient.waitForOnPageFinished();
|
|
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
}
|
|
|
|
public void testRemoveObjectNotAdded() throws Throwable {
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().removeJavascriptInterface("foo");
|
|
getWebView().reload();
|
|
}
|
|
});
|
|
mWebViewClient.waitForOnPageFinished();
|
|
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo"));
|
|
}
|
|
|
|
public void testTypeOfMethod() throws Throwable {
|
|
assertEquals("function",
|
|
executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
|
|
}
|
|
|
|
public void testTypeOfInvalidMethod() throws Throwable {
|
|
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
|
|
}
|
|
|
|
public void testCallingInvalidMethodRaisesException() throws Throwable {
|
|
assertRaisesException("testController.foo()");
|
|
}
|
|
|
|
public void testUncaughtJavaExceptionRaisesJavaException() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public void method() { throw new RuntimeException("foo"); }
|
|
}, "testObject");
|
|
assertRaisesException("testObject.method()");
|
|
}
|
|
|
|
// Note that this requires that we can pass a JavaScript string to Java.
|
|
public void testTypeOfStaticMethod() throws Throwable {
|
|
injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
|
|
executeJavaScript("testController.setStringValue(typeof testObject.staticMethod)");
|
|
assertEquals("function", mTestController.waitForStringValue());
|
|
}
|
|
|
|
// Note that this requires that we can pass a JavaScript string to Java.
|
|
public void testCallStaticMethod() throws Throwable {
|
|
injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
|
|
executeJavaScript("testController.setStringValue(testObject.staticMethod())");
|
|
assertEquals("foo", mTestController.waitForStringValue());
|
|
}
|
|
|
|
public void testPrivateMethodNotExposed() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
private void method() {}
|
|
}, "testObject");
|
|
assertEquals("undefined",
|
|
executeJavaScriptAndGetStringResult("typeof testObject.method"));
|
|
}
|
|
|
|
public void testReplaceInjectedObject() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public void method() { mTestController.setStringValue("object 1"); }
|
|
}, "testObject");
|
|
executeJavaScript("testObject.method()");
|
|
assertEquals("object 1", mTestController.waitForStringValue());
|
|
|
|
injectObjectAndReload(new Object() {
|
|
public void method() { mTestController.setStringValue("object 2"); }
|
|
}, "testObject");
|
|
executeJavaScript("testObject.method()");
|
|
assertEquals("object 2", mTestController.waitForStringValue());
|
|
}
|
|
|
|
public void testInjectNullObjectIsIgnored() throws Throwable {
|
|
injectObjectAndReload(null, "testObject");
|
|
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
}
|
|
|
|
public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable {
|
|
injectObjectAndReload(new Object(), "testObject");
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
injectObjectAndReload(null, "testObject");
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
|
|
}
|
|
|
|
public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public void method() { mTestController.setStringValue("0 args"); }
|
|
public void method(int x) { mTestController.setStringValue("1 arg"); }
|
|
public void method(int x, int y) { mTestController.setStringValue("2 args"); }
|
|
}, "testObject");
|
|
executeJavaScript("testObject.method()");
|
|
assertEquals("0 args", mTestController.waitForStringValue());
|
|
executeJavaScript("testObject.method(42)");
|
|
assertEquals("1 arg", mTestController.waitForStringValue());
|
|
executeJavaScript("testObject.method(null)");
|
|
assertEquals("1 arg", mTestController.waitForStringValue());
|
|
executeJavaScript("testObject.method(undefined)");
|
|
assertEquals("1 arg", mTestController.waitForStringValue());
|
|
executeJavaScript("testObject.method(42, 42)");
|
|
assertEquals("2 args", mTestController.waitForStringValue());
|
|
}
|
|
|
|
public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
|
|
assertRaisesException("testController.setIntValue()");
|
|
assertRaisesException("testController.setIntValue(42, 42)");
|
|
}
|
|
|
|
public void testObjectPersistsAcrossPageLoads() throws Throwable {
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().reload();
|
|
}
|
|
});
|
|
mWebViewClient.waitForOnPageFinished();
|
|
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
|
|
}
|
|
|
|
public void testSameObjectInjectedMultipleTimes() throws Throwable {
|
|
class TestObject {
|
|
private int mNumMethodInvocations;
|
|
public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
|
|
}
|
|
final TestObject testObject = new TestObject();
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().addJavascriptInterface(testObject, "testObject1");
|
|
getWebView().addJavascriptInterface(testObject, "testObject2");
|
|
getWebView().reload();
|
|
}
|
|
});
|
|
mWebViewClient.waitForOnPageFinished();
|
|
executeJavaScript("testObject1.method()");
|
|
assertEquals(1, mTestController.waitForIntValue());
|
|
executeJavaScript("testObject2.method()");
|
|
assertEquals(2, mTestController.waitForIntValue());
|
|
}
|
|
|
|
public void testCallMethodOnReturnedObject() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public Object getInnerObject() {
|
|
return new Object() {
|
|
public void method(int x) { mTestController.setIntValue(x); }
|
|
};
|
|
}
|
|
}, "testObject");
|
|
executeJavaScript("testObject.getInnerObject().method(42)");
|
|
assertEquals(42, mTestController.waitForIntValue());
|
|
}
|
|
|
|
public void testReturnedObjectInjectedElsewhere() throws Throwable {
|
|
class InnerObject {
|
|
private int mNumMethodInvocations;
|
|
public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
|
|
}
|
|
final InnerObject innerObject = new InnerObject();
|
|
final Object object = new Object() {
|
|
public InnerObject getInnerObject() {
|
|
return innerObject;
|
|
}
|
|
};
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
getWebView().addJavascriptInterface(object, "testObject");
|
|
getWebView().addJavascriptInterface(innerObject, "innerObject");
|
|
getWebView().reload();
|
|
}
|
|
});
|
|
mWebViewClient.waitForOnPageFinished();
|
|
executeJavaScript("testObject.getInnerObject().method()");
|
|
assertEquals(1, mTestController.waitForIntValue());
|
|
executeJavaScript("innerObject.method()");
|
|
assertEquals(2, mTestController.waitForIntValue());
|
|
}
|
|
|
|
public void testMethodInvokedOnBackgroundThread() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public void captureThreadId() {
|
|
mTestController.setLongValue(Thread.currentThread().getId());
|
|
}
|
|
}, "testObject");
|
|
executeJavaScript("testObject.captureThreadId()");
|
|
final long threadId = mTestController.waitForLongValue();
|
|
assertFalse(threadId == Thread.currentThread().getId());
|
|
runTestOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
assertFalse(threadId == Thread.currentThread().getId());
|
|
}
|
|
});
|
|
}
|
|
|
|
public void testPublicInheritedMethod() throws Throwable {
|
|
class Base {
|
|
public void method(int x) { mTestController.setIntValue(x); }
|
|
}
|
|
class Derived extends Base {
|
|
}
|
|
injectObjectAndReload(new Derived(), "testObject");
|
|
assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method"));
|
|
executeJavaScript("testObject.method(42)");
|
|
assertEquals(42, mTestController.waitForIntValue());
|
|
}
|
|
|
|
public void testPrivateInheritedMethod() throws Throwable {
|
|
class Base {
|
|
private void method() {}
|
|
}
|
|
class Derived extends Base {
|
|
}
|
|
injectObjectAndReload(new Derived(), "testObject");
|
|
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method"));
|
|
}
|
|
|
|
public void testOverriddenMethod() throws Throwable {
|
|
class Base {
|
|
public void method() { mTestController.setStringValue("base"); }
|
|
}
|
|
class Derived extends Base {
|
|
public void method() { mTestController.setStringValue("derived"); }
|
|
}
|
|
injectObjectAndReload(new Derived(), "testObject");
|
|
executeJavaScript("testObject.method()");
|
|
assertEquals("derived", mTestController.waitForStringValue());
|
|
}
|
|
|
|
public void testEnumerateMembers() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public void method() {}
|
|
private void privateMethod() {}
|
|
public int field;
|
|
private int privateField;
|
|
}, "testObject");
|
|
executeJavaScript(
|
|
"var result = \"\"; " +
|
|
"for (x in testObject) { result += \" \" + x } " +
|
|
"testController.setStringValue(result);");
|
|
// LIVECONNECT_COMPLIANCE: Should be able to enumerate members.
|
|
assertEquals("", mTestController.waitForStringValue());
|
|
}
|
|
|
|
public void testReflectPublicMethod() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public String method() { return "foo"; }
|
|
}, "testObject");
|
|
assertEquals("foo", executeJavaScriptAndGetStringResult(
|
|
"testObject.getClass().getMethod('method', null).invoke(testObject, null)" +
|
|
".toString()"));
|
|
}
|
|
|
|
public void testReflectPublicField() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
public String field = "foo";
|
|
}, "testObject");
|
|
assertEquals("foo", executeJavaScriptAndGetStringResult(
|
|
"testObject.getClass().getField('field').get(testObject).toString()"));
|
|
}
|
|
|
|
public void testReflectPrivateMethodRaisesException() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
private void method() {};
|
|
}, "testObject");
|
|
assertRaisesException("testObject.getClass().getMethod('method', null)");
|
|
// getDeclaredMethod() is able to access a private method, but invoke()
|
|
// throws a Java exception.
|
|
assertRaisesException(
|
|
"testObject.getClass().getDeclaredMethod('method', null).invoke(testObject, null)");
|
|
}
|
|
|
|
public void testReflectPrivateFieldRaisesException() throws Throwable {
|
|
injectObjectAndReload(new Object() {
|
|
private int field;
|
|
}, "testObject");
|
|
assertRaisesException("testObject.getClass().getField('field')");
|
|
// getDeclaredField() is able to access a private field, but getInt()
|
|
// throws a Java exception.
|
|
assertRaisesException(
|
|
"testObject.getClass().getDeclaredField('field').getInt(testObject)");
|
|
}
|
|
}
|