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