• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 /**
18  * Part of the test suite for the WebView's Java Bridge. Tests a number of features including ...
19  * - The type of injected objects
20  * - The type of their methods
21  * - Replacing objects
22  * - Removing objects
23  * - Access control
24  * - Calling methods on returned objects
25  * - Multiply injected objects
26  * - Threading
27  * - Inheritance
28  *
29  * To run this test ...
30  *  adb shell am instrument -w -e class com.android.webviewtests.JavaBridgeBasicsTest \
31  *     com.android.webviewtests/android.test.InstrumentationTestRunner
32  */
33 
34 package com.android.webviewtests;
35 
36 public class JavaBridgeBasicsTest extends JavaBridgeTestBase {
37     private class TestController extends Controller {
38         private int mIntValue;
39         private long mLongValue;
40         private String mStringValue;
41         private boolean mBooleanValue;
42 
setIntValue(int x)43         public synchronized void setIntValue(int x) {
44             mIntValue = x;
45             notifyResultIsReady();
46         }
setLongValue(long x)47         public synchronized void setLongValue(long x) {
48             mLongValue = x;
49             notifyResultIsReady();
50         }
setStringValue(String x)51         public synchronized void setStringValue(String x) {
52             mStringValue = x;
53             notifyResultIsReady();
54         }
setBooleanValue(boolean x)55         public synchronized void setBooleanValue(boolean x) {
56             mBooleanValue = x;
57             notifyResultIsReady();
58         }
59 
waitForIntValue()60         public synchronized int waitForIntValue() {
61             waitForResult();
62             return mIntValue;
63         }
waitForLongValue()64         public synchronized long waitForLongValue() {
65             waitForResult();
66             return mLongValue;
67         }
waitForStringValue()68         public synchronized String waitForStringValue() {
69             waitForResult();
70             return mStringValue;
71         }
waitForBooleanValue()72         public synchronized boolean waitForBooleanValue() {
73             waitForResult();
74             return mBooleanValue;
75         }
76     }
77 
78     private static class ObjectWithStaticMethod {
staticMethod()79         public static String staticMethod() {
80             return "foo";
81         }
82     }
83 
84     TestController mTestController;
85 
86     @Override
setUp()87     protected void setUp() throws Exception {
88         super.setUp();
89         mTestController = new TestController();
90         setUpWebView(mTestController, "testController");
91     }
92 
93     // Note that this requires that we can pass a JavaScript string to Java.
executeJavaScriptAndGetStringResult(String script)94     protected String executeJavaScriptAndGetStringResult(String script) throws Throwable {
95         executeJavaScript("testController.setStringValue(" + script + ");");
96         return mTestController.waitForStringValue();
97     }
98 
injectObjectAndReload(final Object object, final String name)99     protected void injectObjectAndReload(final Object object, final String name) throws Throwable {
100         runTestOnUiThread(new Runnable() {
101             @Override
102             public void run() {
103                 getWebView().addJavascriptInterface(object, name);
104                 getWebView().reload();
105             }
106         });
107         mWebViewClient.waitForOnPageFinished();
108     }
109 
110     // Note that this requires that we can pass a JavaScript boolean to Java.
assertRaisesException(String script)111     private void assertRaisesException(String script) throws Throwable {
112         executeJavaScript("try {" +
113                           script + ";" +
114                           "  testController.setBooleanValue(false);" +
115                           "} catch (exception) {" +
116                           "  testController.setBooleanValue(true);" +
117                           "}");
118         assertTrue(mTestController.waitForBooleanValue());
119     }
120 
testTypeOfInjectedObject()121     public void testTypeOfInjectedObject() throws Throwable {
122         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
123     }
124 
testAdditionNotReflectedUntilReload()125     public void testAdditionNotReflectedUntilReload() throws Throwable {
126         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
127         runTestOnUiThread(new Runnable() {
128             @Override
129             public void run() {
130                 getWebView().addJavascriptInterface(new Object(), "testObject");
131             }
132         });
133         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
134         runTestOnUiThread(new Runnable() {
135             @Override
136             public void run() {
137                 getWebView().reload();
138             }
139         });
140         mWebViewClient.waitForOnPageFinished();
141         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
142     }
143 
testRemovalNotReflectedUntilReload()144     public void testRemovalNotReflectedUntilReload() throws Throwable {
145         injectObjectAndReload(new Object(), "testObject");
146         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
147         runTestOnUiThread(new Runnable() {
148             @Override
149             public void run() {
150                 getWebView().removeJavascriptInterface("testObject");
151             }
152         });
153         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
154         runTestOnUiThread(new Runnable() {
155             @Override
156             public void run() {
157                 getWebView().reload();
158             }
159         });
160         mWebViewClient.waitForOnPageFinished();
161         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
162     }
163 
testRemoveObjectNotAdded()164     public void testRemoveObjectNotAdded() throws Throwable {
165         runTestOnUiThread(new Runnable() {
166             @Override
167             public void run() {
168                 getWebView().removeJavascriptInterface("foo");
169                 getWebView().reload();
170             }
171         });
172         mWebViewClient.waitForOnPageFinished();
173         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo"));
174     }
175 
testTypeOfMethod()176     public void testTypeOfMethod() throws Throwable {
177         assertEquals("function",
178                 executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
179     }
180 
testTypeOfInvalidMethod()181     public void testTypeOfInvalidMethod() throws Throwable {
182         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
183     }
184 
testCallingInvalidMethodRaisesException()185     public void testCallingInvalidMethodRaisesException() throws Throwable {
186         assertRaisesException("testController.foo()");
187     }
188 
testUncaughtJavaExceptionRaisesJavaException()189     public void testUncaughtJavaExceptionRaisesJavaException() throws Throwable {
190         injectObjectAndReload(new Object() {
191             public void method() { throw new RuntimeException("foo"); }
192         }, "testObject");
193         assertRaisesException("testObject.method()");
194     }
195 
196     // Note that this requires that we can pass a JavaScript string to Java.
testTypeOfStaticMethod()197     public void testTypeOfStaticMethod() throws Throwable {
198         injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
199         executeJavaScript("testController.setStringValue(typeof testObject.staticMethod)");
200         assertEquals("function", mTestController.waitForStringValue());
201     }
202 
203     // Note that this requires that we can pass a JavaScript string to Java.
testCallStaticMethod()204     public void testCallStaticMethod() throws Throwable {
205         injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
206         executeJavaScript("testController.setStringValue(testObject.staticMethod())");
207         assertEquals("foo", mTestController.waitForStringValue());
208     }
209 
testPrivateMethodNotExposed()210     public void testPrivateMethodNotExposed() throws Throwable {
211         injectObjectAndReload(new Object() {
212             private void method() {}
213         }, "testObject");
214         assertEquals("undefined",
215                 executeJavaScriptAndGetStringResult("typeof testObject.method"));
216     }
217 
testReplaceInjectedObject()218     public void testReplaceInjectedObject() throws Throwable {
219         injectObjectAndReload(new Object() {
220             public void method() { mTestController.setStringValue("object 1"); }
221         }, "testObject");
222         executeJavaScript("testObject.method()");
223         assertEquals("object 1", mTestController.waitForStringValue());
224 
225         injectObjectAndReload(new Object() {
226             public void method() { mTestController.setStringValue("object 2"); }
227         }, "testObject");
228         executeJavaScript("testObject.method()");
229         assertEquals("object 2", mTestController.waitForStringValue());
230     }
231 
testInjectNullObjectIsIgnored()232     public void testInjectNullObjectIsIgnored() throws Throwable {
233         injectObjectAndReload(null, "testObject");
234         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
235     }
236 
testReplaceInjectedObjectWithNullObjectIsIgnored()237     public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable {
238         injectObjectAndReload(new Object(), "testObject");
239         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
240         injectObjectAndReload(null, "testObject");
241         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
242     }
243 
testCallOverloadedMethodWithDifferentNumberOfArguments()244     public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable {
245         injectObjectAndReload(new Object() {
246             public void method() { mTestController.setStringValue("0 args"); }
247             public void method(int x) { mTestController.setStringValue("1 arg"); }
248             public void method(int x, int y) { mTestController.setStringValue("2 args"); }
249         }, "testObject");
250         executeJavaScript("testObject.method()");
251         assertEquals("0 args", mTestController.waitForStringValue());
252         executeJavaScript("testObject.method(42)");
253         assertEquals("1 arg", mTestController.waitForStringValue());
254         executeJavaScript("testObject.method(null)");
255         assertEquals("1 arg", mTestController.waitForStringValue());
256         executeJavaScript("testObject.method(undefined)");
257         assertEquals("1 arg", mTestController.waitForStringValue());
258         executeJavaScript("testObject.method(42, 42)");
259         assertEquals("2 args", mTestController.waitForStringValue());
260     }
261 
testCallMethodWithWrongNumberOfArgumentsRaisesException()262     public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
263         assertRaisesException("testController.setIntValue()");
264         assertRaisesException("testController.setIntValue(42, 42)");
265     }
266 
testObjectPersistsAcrossPageLoads()267     public void testObjectPersistsAcrossPageLoads() throws Throwable {
268         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
269         runTestOnUiThread(new Runnable() {
270             @Override
271             public void run() {
272                 getWebView().reload();
273             }
274         });
275         mWebViewClient.waitForOnPageFinished();
276         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
277     }
278 
testSameObjectInjectedMultipleTimes()279     public void testSameObjectInjectedMultipleTimes() throws Throwable {
280         class TestObject {
281             private int mNumMethodInvocations;
282             public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
283         }
284         final TestObject testObject = new TestObject();
285         runTestOnUiThread(new Runnable() {
286             @Override
287             public void run() {
288                 getWebView().addJavascriptInterface(testObject, "testObject1");
289                 getWebView().addJavascriptInterface(testObject, "testObject2");
290                 getWebView().reload();
291             }
292         });
293         mWebViewClient.waitForOnPageFinished();
294         executeJavaScript("testObject1.method()");
295         assertEquals(1, mTestController.waitForIntValue());
296         executeJavaScript("testObject2.method()");
297         assertEquals(2, mTestController.waitForIntValue());
298     }
299 
testCallMethodOnReturnedObject()300     public void testCallMethodOnReturnedObject() throws Throwable {
301         injectObjectAndReload(new Object() {
302             public Object getInnerObject() {
303                 return new Object() {
304                     public void method(int x) { mTestController.setIntValue(x); }
305                 };
306             }
307         }, "testObject");
308         executeJavaScript("testObject.getInnerObject().method(42)");
309         assertEquals(42, mTestController.waitForIntValue());
310     }
311 
testReturnedObjectInjectedElsewhere()312     public void testReturnedObjectInjectedElsewhere() throws Throwable {
313         class InnerObject {
314             private int mNumMethodInvocations;
315             public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
316         }
317         final InnerObject innerObject = new InnerObject();
318         final Object object = new Object() {
319             public InnerObject getInnerObject() {
320                 return innerObject;
321             }
322         };
323         runTestOnUiThread(new Runnable() {
324             @Override
325             public void run() {
326                 getWebView().addJavascriptInterface(object, "testObject");
327                 getWebView().addJavascriptInterface(innerObject, "innerObject");
328                 getWebView().reload();
329             }
330         });
331         mWebViewClient.waitForOnPageFinished();
332         executeJavaScript("testObject.getInnerObject().method()");
333         assertEquals(1, mTestController.waitForIntValue());
334         executeJavaScript("innerObject.method()");
335         assertEquals(2, mTestController.waitForIntValue());
336     }
337 
testMethodInvokedOnBackgroundThread()338     public void testMethodInvokedOnBackgroundThread() throws Throwable {
339         injectObjectAndReload(new Object() {
340             public void captureThreadId() {
341                 mTestController.setLongValue(Thread.currentThread().getId());
342             }
343         }, "testObject");
344         executeJavaScript("testObject.captureThreadId()");
345         final long threadId = mTestController.waitForLongValue();
346         assertFalse(threadId == Thread.currentThread().getId());
347         runTestOnUiThread(new Runnable() {
348             @Override
349             public void run() {
350                 assertFalse(threadId == Thread.currentThread().getId());
351             }
352         });
353     }
354 
testPublicInheritedMethod()355     public void testPublicInheritedMethod() throws Throwable {
356         class Base {
357             public void method(int x) { mTestController.setIntValue(x); }
358         }
359         class Derived extends Base {
360         }
361         injectObjectAndReload(new Derived(), "testObject");
362         assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method"));
363         executeJavaScript("testObject.method(42)");
364         assertEquals(42, mTestController.waitForIntValue());
365     }
366 
testPrivateInheritedMethod()367     public void testPrivateInheritedMethod() throws Throwable {
368         class Base {
369             private void method() {}
370         }
371         class Derived extends Base {
372         }
373         injectObjectAndReload(new Derived(), "testObject");
374         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method"));
375     }
376 
testOverriddenMethod()377     public void testOverriddenMethod() throws Throwable {
378         class Base {
379             public void method() { mTestController.setStringValue("base"); }
380         }
381         class Derived extends Base {
382             public void method() { mTestController.setStringValue("derived"); }
383         }
384         injectObjectAndReload(new Derived(), "testObject");
385         executeJavaScript("testObject.method()");
386         assertEquals("derived", mTestController.waitForStringValue());
387     }
388 
testEnumerateMembers()389     public void testEnumerateMembers() throws Throwable {
390         injectObjectAndReload(new Object() {
391             public void method() {}
392             private void privateMethod() {}
393             public int field;
394             private int privateField;
395         }, "testObject");
396         executeJavaScript(
397                 "var result = \"\"; " +
398                 "for (x in testObject) { result += \" \" + x } " +
399                 "testController.setStringValue(result);");
400         // LIVECONNECT_COMPLIANCE: Should be able to enumerate members.
401         assertEquals("", mTestController.waitForStringValue());
402     }
403 
testReflectPublicMethod()404     public void testReflectPublicMethod() throws Throwable {
405         injectObjectAndReload(new Object() {
406             public String method() { return "foo"; }
407         }, "testObject");
408         assertEquals("foo", executeJavaScriptAndGetStringResult(
409                 "testObject.getClass().getMethod('method', null).invoke(testObject, null)" +
410                 ".toString()"));
411     }
412 
testReflectPublicField()413     public void testReflectPublicField() throws Throwable {
414         injectObjectAndReload(new Object() {
415             public String field = "foo";
416         }, "testObject");
417         assertEquals("foo", executeJavaScriptAndGetStringResult(
418                 "testObject.getClass().getField('field').get(testObject).toString()"));
419     }
420 
testReflectPrivateMethodRaisesException()421     public void testReflectPrivateMethodRaisesException() throws Throwable {
422         injectObjectAndReload(new Object() {
423             private void method() {};
424         }, "testObject");
425         assertRaisesException("testObject.getClass().getMethod('method', null)");
426         // getDeclaredMethod() is able to access a private method, but invoke()
427         // throws a Java exception.
428         assertRaisesException(
429                 "testObject.getClass().getDeclaredMethod('method', null).invoke(testObject, null)");
430     }
431 
testReflectPrivateFieldRaisesException()432     public void testReflectPrivateFieldRaisesException() throws Throwable {
433         injectObjectAndReload(new Object() {
434             private int field;
435         }, "testObject");
436         assertRaisesException("testObject.getClass().getField('field')");
437         // getDeclaredField() is able to access a private field, but getInt()
438         // throws a Java exception.
439         assertRaisesException(
440                 "testObject.getClass().getDeclaredField('field').getInt(testObject)");
441     }
442 }
443