JavaBridgeBasicsTest.java revision f2477e01787aa58f445919b809d89e252beef54f
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.browser; 6 7import android.test.suitebuilder.annotation.SmallTest; 8 9import org.chromium.base.test.util.Feature; 10import org.chromium.content.browser.test.util.TestCallbackHelperContainer; 11 12import java.lang.annotation.Annotation; 13import java.lang.annotation.ElementType; 14import java.lang.annotation.Retention; 15import java.lang.annotation.RetentionPolicy; 16import java.lang.annotation.Target; 17 18/** 19 * Part of the test suite for the Java Bridge. Tests a number of features including ... 20 * - The type of injected objects 21 * - The type of their methods 22 * - Replacing objects 23 * - Removing objects 24 * - Access control 25 * - Calling methods on returned objects 26 * - Multiply injected objects 27 * - Threading 28 * - Inheritance 29 */ 30public class JavaBridgeBasicsTest extends JavaBridgeTestBase { 31 private class TestController extends Controller { 32 private int mIntValue; 33 private long mLongValue; 34 private String mStringValue; 35 private boolean mBooleanValue; 36 37 public synchronized void setIntValue(int x) { 38 mIntValue = x; 39 notifyResultIsReady(); 40 } 41 public synchronized void setLongValue(long x) { 42 mLongValue = x; 43 notifyResultIsReady(); 44 } 45 public synchronized void setStringValue(String x) { 46 mStringValue = x; 47 notifyResultIsReady(); 48 } 49 public synchronized void setBooleanValue(boolean x) { 50 mBooleanValue = x; 51 notifyResultIsReady(); 52 } 53 54 public synchronized int waitForIntValue() { 55 waitForResult(); 56 return mIntValue; 57 } 58 public synchronized long waitForLongValue() { 59 waitForResult(); 60 return mLongValue; 61 } 62 public synchronized String waitForStringValue() { 63 waitForResult(); 64 return mStringValue; 65 } 66 public synchronized boolean waitForBooleanValue() { 67 waitForResult(); 68 return mBooleanValue; 69 } 70 } 71 72 private static class ObjectWithStaticMethod { 73 public static String staticMethod() { 74 return "foo"; 75 } 76 } 77 78 TestController mTestController; 79 80 @Override 81 protected void setUp() throws Exception { 82 super.setUp(); 83 mTestController = new TestController(); 84 setUpContentView(mTestController, "testController"); 85 } 86 87 // Note that this requires that we can pass a JavaScript string to Java. 88 protected String executeJavaScriptAndGetStringResult(String script) throws Throwable { 89 executeJavaScript("testController.setStringValue(" + script + ");"); 90 return mTestController.waitForStringValue(); 91 } 92 93 protected void injectObjectAndReload(final Object object, final String name) throws Throwable { 94 injectObjectAndReload(object, name, null); 95 } 96 97 protected void injectObjectAndReload(final Object object, final String name, 98 final Class<? extends Annotation> requiredAnnotation) throws Throwable { 99 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 100 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 101 int currentCallCount = onPageFinishedHelper.getCallCount(); 102 runTestOnUiThread(new Runnable() { 103 @Override 104 public void run() { 105 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(object, 106 name, requiredAnnotation); 107 getContentView().getContentViewCore().reload(true); 108 } 109 }); 110 onPageFinishedHelper.waitForCallback(currentCallCount); 111 } 112 113 // Note that this requires that we can pass a JavaScript boolean to Java. 114 private void assertRaisesException(String script) throws Throwable { 115 executeJavaScript("try {" + 116 script + ";" + 117 " testController.setBooleanValue(false);" + 118 "} catch (exception) {" + 119 " testController.setBooleanValue(true);" + 120 "}"); 121 assertTrue(mTestController.waitForBooleanValue()); 122 } 123 124 @SmallTest 125 @Feature({"AndroidWebView", "Android-JavaBridge"}) 126 public void testTypeOfInjectedObject() throws Throwable { 127 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController")); 128 } 129 130 @SmallTest 131 @Feature({"AndroidWebView", "Android-JavaBridge"}) 132 public void testAdditionNotReflectedUntilReload() throws Throwable { 133 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject")); 134 runTestOnUiThread(new Runnable() { 135 @Override 136 public void run() { 137 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface( 138 new Object(), "testObject", null); 139 } 140 }); 141 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject")); 142 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 143 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 144 int currentCallCount = onPageFinishedHelper.getCallCount(); 145 runTestOnUiThread(new Runnable() { 146 @Override 147 public void run() { 148 getContentView().getContentViewCore().reload(true); 149 } 150 }); 151 onPageFinishedHelper.waitForCallback(currentCallCount); 152 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject")); 153 } 154 155 @SmallTest 156 @Feature({"AndroidWebView", "Android-JavaBridge"}) 157 public void testRemovalNotReflectedUntilReload() throws Throwable { 158 injectObjectAndReload(new Object(), "testObject"); 159 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject")); 160 runTestOnUiThread(new Runnable() { 161 @Override 162 public void run() { 163 getContentView().getContentViewCore().removeJavascriptInterface("testObject"); 164 } 165 }); 166 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject")); 167 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 168 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 169 int currentCallCount = onPageFinishedHelper.getCallCount(); 170 runTestOnUiThread(new Runnable() { 171 @Override 172 public void run() { 173 getContentView().getContentViewCore().reload(true); 174 } 175 }); 176 onPageFinishedHelper.waitForCallback(currentCallCount); 177 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject")); 178 } 179 180 @SmallTest 181 @Feature({"AndroidWebView", "Android-JavaBridge"}) 182 public void testRemoveObjectNotAdded() throws Throwable { 183 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 184 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 185 int currentCallCount = onPageFinishedHelper.getCallCount(); 186 runTestOnUiThread(new Runnable() { 187 @Override 188 public void run() { 189 getContentView().getContentViewCore().removeJavascriptInterface("foo"); 190 getContentView().getContentViewCore().reload(true); 191 } 192 }); 193 onPageFinishedHelper.waitForCallback(currentCallCount); 194 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo")); 195 } 196 197 @SmallTest 198 @Feature({"AndroidWebView", "Android-JavaBridge"}) 199 public void testTypeOfMethod() throws Throwable { 200 assertEquals("function", 201 executeJavaScriptAndGetStringResult("typeof testController.setStringValue")); 202 } 203 204 @SmallTest 205 @Feature({"AndroidWebView", "Android-JavaBridge"}) 206 public void testTypeOfInvalidMethod() throws Throwable { 207 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo")); 208 } 209 210 @SmallTest 211 @Feature({"AndroidWebView", "Android-JavaBridge"}) 212 public void testCallingInvalidMethodRaisesException() throws Throwable { 213 assertRaisesException("testController.foo()"); 214 } 215 216 @SmallTest 217 @Feature({"AndroidWebView", "Android-JavaBridge"}) 218 public void testUncaughtJavaExceptionRaisesJavaScriptException() throws Throwable { 219 injectObjectAndReload(new Object() { 220 public void method() { throw new RuntimeException("foo"); } 221 }, "testObject"); 222 assertRaisesException("testObject.method()"); 223 } 224 225 // Note that this requires that we can pass a JavaScript string to Java. 226 @SmallTest 227 @Feature({"AndroidWebView", "Android-JavaBridge"}) 228 public void testTypeOfStaticMethod() throws Throwable { 229 injectObjectAndReload(new ObjectWithStaticMethod(), "testObject"); 230 executeJavaScript("testController.setStringValue(typeof testObject.staticMethod)"); 231 assertEquals("function", mTestController.waitForStringValue()); 232 } 233 234 // Note that this requires that we can pass a JavaScript string to Java. 235 @SmallTest 236 @Feature({"AndroidWebView", "Android-JavaBridge"}) 237 public void testCallStaticMethod() throws Throwable { 238 injectObjectAndReload(new ObjectWithStaticMethod(), "testObject"); 239 executeJavaScript("testController.setStringValue(testObject.staticMethod())"); 240 assertEquals("foo", mTestController.waitForStringValue()); 241 } 242 243 @SmallTest 244 @Feature({"AndroidWebView", "Android-JavaBridge"}) 245 public void testPrivateMethodNotExposed() throws Throwable { 246 injectObjectAndReload(new Object() { 247 private void method() {} 248 protected void method2() {} 249 }, "testObject"); 250 assertEquals("undefined", 251 executeJavaScriptAndGetStringResult("typeof testObject.method")); 252 assertEquals("undefined", 253 executeJavaScriptAndGetStringResult("typeof testObject.method2")); 254 } 255 256 @SmallTest 257 @Feature({"AndroidWebView", "Android-JavaBridge"}) 258 public void testReplaceInjectedObject() throws Throwable { 259 injectObjectAndReload(new Object() { 260 public void method() { mTestController.setStringValue("object 1"); } 261 }, "testObject"); 262 executeJavaScript("testObject.method()"); 263 assertEquals("object 1", mTestController.waitForStringValue()); 264 265 injectObjectAndReload(new Object() { 266 public void method() { mTestController.setStringValue("object 2"); } 267 }, "testObject"); 268 executeJavaScript("testObject.method()"); 269 assertEquals("object 2", mTestController.waitForStringValue()); 270 } 271 272 @SmallTest 273 @Feature({"AndroidWebView", "Android-JavaBridge"}) 274 public void testInjectNullObjectIsIgnored() throws Throwable { 275 injectObjectAndReload(null, "testObject"); 276 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject")); 277 } 278 279 @SmallTest 280 @Feature({"AndroidWebView", "Android-JavaBridge"}) 281 public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable { 282 injectObjectAndReload(new Object(), "testObject"); 283 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject")); 284 injectObjectAndReload(null, "testObject"); 285 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject")); 286 } 287 288 @SmallTest 289 @Feature({"AndroidWebView", "Android-JavaBridge"}) 290 public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable { 291 injectObjectAndReload(new Object() { 292 public void method() { mTestController.setStringValue("0 args"); } 293 public void method(int x) { mTestController.setStringValue("1 arg"); } 294 public void method(int x, int y) { mTestController.setStringValue("2 args"); } 295 }, "testObject"); 296 executeJavaScript("testObject.method()"); 297 assertEquals("0 args", mTestController.waitForStringValue()); 298 executeJavaScript("testObject.method(42)"); 299 assertEquals("1 arg", mTestController.waitForStringValue()); 300 executeJavaScript("testObject.method(null)"); 301 assertEquals("1 arg", mTestController.waitForStringValue()); 302 executeJavaScript("testObject.method(undefined)"); 303 assertEquals("1 arg", mTestController.waitForStringValue()); 304 executeJavaScript("testObject.method(42, 42)"); 305 assertEquals("2 args", mTestController.waitForStringValue()); 306 } 307 308 @SmallTest 309 @Feature({"AndroidWebView", "Android-JavaBridge"}) 310 public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable { 311 assertRaisesException("testController.setIntValue()"); 312 assertRaisesException("testController.setIntValue(42, 42)"); 313 } 314 315 @SmallTest 316 @Feature({"AndroidWebView", "Android-JavaBridge"}) 317 public void testObjectPersistsAcrossPageLoads() throws Throwable { 318 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController")); 319 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 320 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 321 int currentCallCount = onPageFinishedHelper.getCallCount(); 322 runTestOnUiThread(new Runnable() { 323 @Override 324 public void run() { 325 getContentView().getContentViewCore().reload(true); 326 } 327 }); 328 onPageFinishedHelper.waitForCallback(currentCallCount); 329 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController")); 330 } 331 332 @SmallTest 333 @Feature({"AndroidWebView", "Android-JavaBridge"}) 334 public void testSameObjectInjectedMultipleTimes() throws Throwable { 335 class TestObject { 336 private int mNumMethodInvocations; 337 public void method() { mTestController.setIntValue(++mNumMethodInvocations); } 338 } 339 final TestObject testObject = new TestObject(); 340 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 341 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 342 int currentCallCount = onPageFinishedHelper.getCallCount(); 343 runTestOnUiThread(new Runnable() { 344 @Override 345 public void run() { 346 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface( 347 testObject, "testObject1", null); 348 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface( 349 testObject, "testObject2", null); 350 getContentView().getContentViewCore().reload(true); 351 } 352 }); 353 onPageFinishedHelper.waitForCallback(currentCallCount); 354 executeJavaScript("testObject1.method()"); 355 assertEquals(1, mTestController.waitForIntValue()); 356 executeJavaScript("testObject2.method()"); 357 assertEquals(2, mTestController.waitForIntValue()); 358 } 359 360 @SmallTest 361 @Feature({"AndroidWebView", "Android-JavaBridge"}) 362 public void testCallMethodOnReturnedObject() throws Throwable { 363 injectObjectAndReload(new Object() { 364 public Object getInnerObject() { 365 return new Object() { 366 public void method(int x) { mTestController.setIntValue(x); } 367 }; 368 } 369 }, "testObject"); 370 executeJavaScript("testObject.getInnerObject().method(42)"); 371 assertEquals(42, mTestController.waitForIntValue()); 372 } 373 374 @SmallTest 375 @Feature({"AndroidWebView", "Android-JavaBridge"}) 376 public void testReturnedObjectInjectedElsewhere() throws Throwable { 377 class InnerObject { 378 private int mNumMethodInvocations; 379 public void method() { mTestController.setIntValue(++mNumMethodInvocations); } 380 } 381 final InnerObject innerObject = new InnerObject(); 382 final Object object = new Object() { 383 public InnerObject getInnerObject() { 384 return innerObject; 385 } 386 }; 387 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 388 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 389 int currentCallCount = onPageFinishedHelper.getCallCount(); 390 runTestOnUiThread(new Runnable() { 391 @Override 392 public void run() { 393 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface( 394 object, "testObject", null); 395 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface( 396 innerObject, "innerObject", null); 397 getContentView().getContentViewCore().reload(true); 398 } 399 }); 400 onPageFinishedHelper.waitForCallback(currentCallCount); 401 executeJavaScript("testObject.getInnerObject().method()"); 402 assertEquals(1, mTestController.waitForIntValue()); 403 executeJavaScript("innerObject.method()"); 404 assertEquals(2, mTestController.waitForIntValue()); 405 } 406 407 @SmallTest 408 @Feature({"AndroidWebView", "Android-JavaBridge"}) 409 public void testMethodInvokedOnBackgroundThread() throws Throwable { 410 injectObjectAndReload(new Object() { 411 public void captureThreadId() { 412 mTestController.setLongValue(Thread.currentThread().getId()); 413 } 414 }, "testObject"); 415 executeJavaScript("testObject.captureThreadId()"); 416 final long threadId = mTestController.waitForLongValue(); 417 assertFalse(threadId == Thread.currentThread().getId()); 418 runTestOnUiThread(new Runnable() { 419 @Override 420 public void run() { 421 assertFalse(threadId == Thread.currentThread().getId()); 422 } 423 }); 424 } 425 426 @SmallTest 427 @Feature({"AndroidWebView", "Android-JavaBridge"}) 428 public void testPublicInheritedMethod() throws Throwable { 429 class Base { 430 public void method(int x) { mTestController.setIntValue(x); } 431 } 432 class Derived extends Base { 433 } 434 injectObjectAndReload(new Derived(), "testObject"); 435 assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method")); 436 executeJavaScript("testObject.method(42)"); 437 assertEquals(42, mTestController.waitForIntValue()); 438 } 439 440 @SmallTest 441 @Feature({"AndroidWebView", "Android-JavaBridge"}) 442 public void testPrivateInheritedMethod() throws Throwable { 443 class Base { 444 private void method() {} 445 } 446 class Derived extends Base { 447 } 448 injectObjectAndReload(new Derived(), "testObject"); 449 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method")); 450 } 451 452 @SmallTest 453 @Feature({"AndroidWebView", "Android-JavaBridge"}) 454 public void testOverriddenMethod() throws Throwable { 455 class Base { 456 public void method() { mTestController.setStringValue("base"); } 457 } 458 class Derived extends Base { 459 @Override 460 public void method() { mTestController.setStringValue("derived"); } 461 } 462 injectObjectAndReload(new Derived(), "testObject"); 463 executeJavaScript("testObject.method()"); 464 assertEquals("derived", mTestController.waitForStringValue()); 465 } 466 467 @SmallTest 468 @Feature({"AndroidWebView", "Android-JavaBridge"}) 469 public void testEnumerateMembers() throws Throwable { 470 injectObjectAndReload(new Object() { 471 public void method() {} 472 private void privateMethod() {} 473 public int field; 474 private int privateField; 475 }, "testObject"); 476 executeJavaScript( 477 "var result = \"\"; " + 478 "for (x in testObject) { result += \" \" + x } " + 479 "testController.setStringValue(result);"); 480 // LIVECONNECT_COMPLIANCE: Should be able to enumerate members. 481 assertEquals("", mTestController.waitForStringValue()); 482 } 483 484 @SmallTest 485 @Feature({"AndroidWebView", "Android-JavaBridge"}) 486 public void testReflectPublicMethod() throws Throwable { 487 injectObjectAndReload(new Object() { 488 public String method() { return "foo"; } 489 }, "testObject"); 490 assertEquals("foo", executeJavaScriptAndGetStringResult( 491 "testObject.getClass().getMethod('method', null).invoke(testObject, null)" + 492 ".toString()")); 493 } 494 495 @SmallTest 496 @Feature({"AndroidWebView", "Android-JavaBridge"}) 497 public void testReflectPublicField() throws Throwable { 498 injectObjectAndReload(new Object() { 499 public String field = "foo"; 500 }, "testObject"); 501 assertEquals("foo", executeJavaScriptAndGetStringResult( 502 "testObject.getClass().getField('field').get(testObject).toString()")); 503 } 504 505 @SmallTest 506 @Feature({"AndroidWebView", "Android-JavaBridge"}) 507 public void testReflectPrivateMethodRaisesException() throws Throwable { 508 injectObjectAndReload(new Object() { 509 private void method() {}; 510 }, "testObject"); 511 assertRaisesException("testObject.getClass().getMethod('method', null)"); 512 // getDeclaredMethod() is able to access a private method, but invoke() 513 // throws a Java exception. 514 assertRaisesException( 515 "testObject.getClass().getDeclaredMethod('method', null).invoke(testObject, null)"); 516 } 517 518 @SmallTest 519 @Feature({"AndroidWebView", "Android-JavaBridge"}) 520 public void testReflectPrivateFieldRaisesException() throws Throwable { 521 injectObjectAndReload(new Object() { 522 private int field; 523 }, "testObject"); 524 assertRaisesException("testObject.getClass().getField('field')"); 525 // getDeclaredField() is able to access a private field, but getInt() 526 // throws a Java exception. 527 assertRaisesException( 528 "testObject.getClass().getDeclaredField('field').getInt(testObject)"); 529 } 530 531 @SmallTest 532 @Feature({"AndroidWebView", "Android-JavaBridge"}) 533 public void testAllowNonAnnotatedMethods() throws Throwable { 534 injectObjectAndReload(new Object() { 535 public String allowed() { return "foo"; } 536 }, "testObject", null); 537 538 // Test calling a method of an explicitly inherited class (Base#allowed()). 539 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()")); 540 541 // Test calling a method of an implicitly inherited class (Object#getClass()). 542 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject.getClass()")); 543 } 544 545 @SmallTest 546 @Feature({"AndroidWebView", "Android-JavaBridge"}) 547 public void testAllowOnlyAnnotatedMethods() throws Throwable { 548 injectObjectAndReload(new Object() { 549 @JavascriptInterface 550 public String allowed() { return "foo"; } 551 552 public String disallowed() { return "bar"; } 553 }, "testObject", JavascriptInterface.class); 554 555 // getClass() is an Object method and does not have the @JavascriptInterface annotation and 556 // should not be able to be called. 557 assertRaisesException("testObject.getClass()"); 558 assertEquals("undefined", executeJavaScriptAndGetStringResult( 559 "typeof testObject.getClass")); 560 561 // allowed() is marked with the @JavascriptInterface annotation and should be allowed to be 562 // called. 563 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()")); 564 565 // disallowed() is not marked with the @JavascriptInterface annotation and should not be 566 // able to be called. 567 assertRaisesException("testObject.disallowed()"); 568 assertEquals("undefined", executeJavaScriptAndGetStringResult( 569 "typeof testObject.disallowed")); 570 } 571 572 @SmallTest 573 @Feature({"AndroidWebView", "Android-JavaBridge"}) 574 public void testAnnotationRequirementRetainsPropertyAcrossObjects() throws Throwable { 575 class Test { 576 @JavascriptInterface 577 public String safe() { return "foo"; } 578 579 public String unsafe() { return "bar"; } 580 } 581 582 class TestReturner { 583 @JavascriptInterface 584 public Test getTest() { return new Test(); } 585 } 586 587 // First test with safe mode off. 588 injectObjectAndReload(new TestReturner(), "unsafeTestObject", null); 589 590 // safe() should be able to be called regardless of whether or not we are in safe mode. 591 assertEquals("foo", executeJavaScriptAndGetStringResult( 592 "unsafeTestObject.getTest().safe()")); 593 // unsafe() should be able to be called because we are not in safe mode. 594 assertEquals("bar", executeJavaScriptAndGetStringResult( 595 "unsafeTestObject.getTest().unsafe()")); 596 597 // Now test with safe mode on. 598 injectObjectAndReload(new TestReturner(), "safeTestObject", JavascriptInterface.class); 599 600 // safe() should be able to be called regardless of whether or not we are in safe mode. 601 assertEquals("foo", executeJavaScriptAndGetStringResult( 602 "safeTestObject.getTest().safe()")); 603 // unsafe() should not be able to be called because we are in safe mode. 604 assertRaisesException("safeTestObject.getTest().unsafe()"); 605 assertEquals("undefined", executeJavaScriptAndGetStringResult( 606 "typeof safeTestObject.getTest().unsafe")); 607 // getClass() is an Object method and does not have the @JavascriptInterface annotation and 608 // should not be able to be called. 609 assertRaisesException("safeTestObject.getTest().getClass()"); 610 assertEquals("undefined", executeJavaScriptAndGetStringResult( 611 "typeof safeTestObject.getTest().getClass")); 612 } 613 614 @SmallTest 615 @Feature({"AndroidWebView", "Android-JavaBridge"}) 616 public void testAnnotationDoesNotGetInherited() throws Throwable { 617 class Base { 618 @JavascriptInterface 619 public void base() { } 620 } 621 622 class Child extends Base { 623 @Override 624 public void base() { } 625 } 626 627 injectObjectAndReload(new Child(), "testObject", JavascriptInterface.class); 628 629 // base() is inherited. The inherited method does not have the @JavascriptInterface 630 // annotation and should not be able to be called. 631 assertRaisesException("testObject.base()"); 632 assertEquals("undefined", executeJavaScriptAndGetStringResult( 633 "typeof testObject.base")); 634 } 635 636 @SuppressWarnings("javadoc") 637 @Retention(RetentionPolicy.RUNTIME) 638 @Target({ElementType.METHOD}) 639 @interface TestAnnotation { 640 } 641 642 @SmallTest 643 @Feature({"AndroidWebView", "Android-JavaBridge"}) 644 public void testCustomAnnotationRestriction() throws Throwable { 645 class Test { 646 @TestAnnotation 647 public String checkTestAnnotationFoo() { return "bar"; } 648 649 @JavascriptInterface 650 public String checkJavascriptInterfaceFoo() { return "bar"; } 651 } 652 653 // Inject javascriptInterfaceObj and require the JavascriptInterface annotation. 654 injectObjectAndReload(new Test(), "javascriptInterfaceObj", JavascriptInterface.class); 655 656 // Test#testAnnotationFoo() should fail, as it isn't annotated with JavascriptInterface. 657 assertRaisesException("javascriptInterfaceObj.checkTestAnnotationFoo()"); 658 assertEquals("undefined", executeJavaScriptAndGetStringResult( 659 "typeof javascriptInterfaceObj.checkTestAnnotationFoo")); 660 661 // Test#javascriptInterfaceFoo() should pass, as it is annotated with JavascriptInterface. 662 assertEquals("bar", executeJavaScriptAndGetStringResult( 663 "javascriptInterfaceObj.checkJavascriptInterfaceFoo()")); 664 665 // Inject testAnnotationObj and require the TestAnnotation annotation. 666 injectObjectAndReload(new Test(), "testAnnotationObj", TestAnnotation.class); 667 668 // Test#testAnnotationFoo() should pass, as it is annotated with TestAnnotation. 669 assertEquals("bar", executeJavaScriptAndGetStringResult( 670 "testAnnotationObj.checkTestAnnotationFoo()")); 671 672 // Test#javascriptInterfaceFoo() should fail, as it isn't annotated with TestAnnotation. 673 assertRaisesException("testAnnotationObj.checkJavascriptInterfaceFoo()"); 674 assertEquals("undefined", executeJavaScriptAndGetStringResult( 675 "typeof testAnnotationObj.checkJavascriptInterfaceFoo")); 676 } 677 678 @SmallTest 679 @Feature({"AndroidWebView", "Android-JavaBridge"}) 680 public void testAddJavascriptInterfaceIsSafeByDefault() throws Throwable { 681 class Test { 682 public String blocked() { return "bar"; } 683 684 @JavascriptInterface 685 public String allowed() { return "bar"; } 686 } 687 688 // Manually inject the Test object, making sure to use the 689 // ContentViewCore#addJavascriptInterface, not the possibly unsafe version. 690 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 691 mTestCallbackHelperContainer.getOnPageFinishedHelper(); 692 int currentCallCount = onPageFinishedHelper.getCallCount(); 693 runTestOnUiThread(new Runnable() { 694 @Override 695 public void run() { 696 getContentView().getContentViewCore().addJavascriptInterface(new Test(), 697 "testObject"); 698 getContentView().getContentViewCore().reload(true); 699 } 700 }); 701 onPageFinishedHelper.waitForCallback(currentCallCount); 702 703 // Test#allowed() should pass, as it is annotated with JavascriptInterface. 704 assertEquals("bar", executeJavaScriptAndGetStringResult( 705 "testObject.allowed()")); 706 707 // Test#blocked() should fail, as it isn't annotated with JavascriptInterface. 708 assertRaisesException("testObject.blocked()"); 709 assertEquals("undefined", executeJavaScriptAndGetStringResult( 710 "typeof testObject.blocked")); 711 } 712} 713