Home | History | Annotate | Download | only in webviewtests
      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 
     43         public synchronized void setIntValue(int x) {
     44             mIntValue = x;
     45             notifyResultIsReady();
     46         }
     47         public synchronized void setLongValue(long x) {
     48             mLongValue = x;
     49             notifyResultIsReady();
     50         }
     51         public synchronized void setStringValue(String x) {
     52             mStringValue = x;
     53             notifyResultIsReady();
     54         }
     55         public synchronized void setBooleanValue(boolean x) {
     56             mBooleanValue = x;
     57             notifyResultIsReady();
     58         }
     59 
     60         public synchronized int waitForIntValue() {
     61             waitForResult();
     62             return mIntValue;
     63         }
     64         public synchronized long waitForLongValue() {
     65             waitForResult();
     66             return mLongValue;
     67         }
     68         public synchronized String waitForStringValue() {
     69             waitForResult();
     70             return mStringValue;
     71         }
     72         public synchronized boolean waitForBooleanValue() {
     73             waitForResult();
     74             return mBooleanValue;
     75         }
     76     }
     77 
     78     private static class ObjectWithStaticMethod {
     79         public static String staticMethod() {
     80             return "foo";
     81         }
     82     }
     83 
     84     TestController mTestController;
     85 
     86     @Override
     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.
     94     protected String executeJavaScriptAndGetStringResult(String script) throws Throwable {
     95         executeJavaScript("testController.setStringValue(" + script + ");");
     96         return mTestController.waitForStringValue();
     97     }
     98 
     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.
    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 
    121     public void testTypeOfInjectedObject() throws Throwable {
    122         assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
    123     }
    124 
    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 
    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 
    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 
    176     public void testTypeOfMethod() throws Throwable {
    177         assertEquals("function",
    178                 executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
    179     }
    180 
    181     public void testTypeOfInvalidMethod() throws Throwable {
    182         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
    183     }
    184 
    185     public void testCallingInvalidMethodRaisesException() throws Throwable {
    186         assertRaisesException("testController.foo()");
    187     }
    188 
    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.
    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.
    204     public void testCallStaticMethod() throws Throwable {
    205         injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
    206         executeJavaScript("testController.setStringValue(testObject.staticMethod())");
    207         assertEquals("foo", mTestController.waitForStringValue());
    208     }
    209 
    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 
    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 
    232     public void testInjectNullObjectIsIgnored() throws Throwable {
    233         injectObjectAndReload(null, "testObject");
    234         assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
    235     }
    236 
    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 
    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 
    262     public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
    263         assertRaisesException("testController.setIntValue()");
    264         assertRaisesException("testController.setIntValue(42, 42)");
    265     }
    266 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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