/* * 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)"); } }