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