1 // Copyright 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.
5 package org.chromium.content.browser;
7 import android.test.suitebuilder.annotation.SmallTest;
9 import org.chromium.base.test.util.Feature;
10 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
11 import org.chromium.content_shell_apk.ContentShellActivity;
13 import java.lang.annotation.Annotation;
14 import java.lang.annotation.ElementType;
15 import java.lang.annotation.Retention;
16 import java.lang.annotation.RetentionPolicy;
17 import java.lang.annotation.Target;
18 import java.lang.ref.WeakReference;
21 * Part of the test suite for the Java Bridge. Tests a number of features including ...
22 * - The type of injected objects
23 * - The type of their methods
27 * - Calling methods on returned objects
28 * - Multiply injected objects
32 public class JavaBridgeBasicsTest extends JavaBridgeTestBase {
33 private class TestController extends Controller {
34 private int mIntValue;
35 private long mLongValue;
36 private String mStringValue;
37 private boolean mBooleanValue;
39 public synchronized void setIntValue(int x) {
41 notifyResultIsReady();
43 public synchronized void setLongValue(long x) {
45 notifyResultIsReady();
47 public synchronized void setStringValue(String x) {
49 notifyResultIsReady();
51 public synchronized void setBooleanValue(boolean x) {
53 notifyResultIsReady();
56 public synchronized int waitForIntValue() {
60 public synchronized long waitForLongValue() {
64 public synchronized String waitForStringValue() {
68 public synchronized boolean waitForBooleanValue() {
73 public synchronized String getStringValue() {
78 private static class ObjectWithStaticMethod {
79 public static String staticMethod() {
84 TestController mTestController;
87 protected void setUp() throws Exception {
89 mTestController = new TestController();
90 setUpContentView(mTestController, "testController");
94 protected ContentShellActivity launchContentShellWithUrl(String url) {
95 // Expose a global function "gc()" into pages.
96 return launchContentShellWithUrlAndCommandLineArgs(
97 url, new String[]{ "--js-flags=--expose-gc" });
100 // Note that this requires that we can pass a JavaScript string to Java.
101 protected String executeJavaScriptAndGetStringResult(String script) throws Throwable {
102 executeJavaScript("testController.setStringValue(" + script + ");");
103 return mTestController.waitForStringValue();
106 protected void injectObjectAndReload(final Object object, final String name) throws Throwable {
107 injectObjectAndReload(object, name, null);
110 protected void injectObjectAndReload(final Object object, final String name,
111 final Class<? extends Annotation> requiredAnnotation) throws Throwable {
112 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
113 mTestCallbackHelperContainer.getOnPageFinishedHelper();
114 int currentCallCount = onPageFinishedHelper.getCallCount();
115 runTestOnUiThread(new Runnable() {
118 getContentViewCore().addPossiblyUnsafeJavascriptInterface(object,
119 name, requiredAnnotation);
120 getContentViewCore().reload(true);
123 onPageFinishedHelper.waitForCallback(currentCallCount);
126 protected void synchronousPageReload() throws Throwable {
127 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
128 mTestCallbackHelperContainer.getOnPageFinishedHelper();
129 int currentCallCount = onPageFinishedHelper.getCallCount();
130 runTestOnUiThread(new Runnable() {
133 getContentViewCore().reload(true);
136 onPageFinishedHelper.waitForCallback(currentCallCount);
139 // Note that this requires that we can pass a JavaScript boolean to Java.
140 private void assertRaisesException(String script) throws Throwable {
141 executeJavaScript("try {" +
143 " testController.setBooleanValue(false);" +
144 "} catch (exception) {" +
145 " testController.setBooleanValue(true);" +
147 assertTrue(mTestController.waitForBooleanValue());
151 @Feature({"AndroidWebView", "Android-JavaBridge"})
152 public void testTypeOfInjectedObject() throws Throwable {
153 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
157 @Feature({"AndroidWebView", "Android-JavaBridge"})
158 public void testAdditionNotReflectedUntilReload() throws Throwable {
159 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
160 runTestOnUiThread(new Runnable() {
163 getContentViewCore().addPossiblyUnsafeJavascriptInterface(
164 new Object(), "testObject", null);
167 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
168 synchronousPageReload();
169 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
173 @Feature({"AndroidWebView", "Android-JavaBridge"})
174 public void testRemovalNotReflectedUntilReload() throws Throwable {
175 injectObjectAndReload(new Object(), "testObject");
176 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
177 runTestOnUiThread(new Runnable() {
180 getContentViewCore().removeJavascriptInterface("testObject");
183 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
184 synchronousPageReload();
185 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
189 @Feature({"AndroidWebView", "Android-JavaBridge"})
190 public void testRemoveObjectNotAdded() throws Throwable {
191 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
192 mTestCallbackHelperContainer.getOnPageFinishedHelper();
193 int currentCallCount = onPageFinishedHelper.getCallCount();
194 runTestOnUiThread(new Runnable() {
197 getContentViewCore().removeJavascriptInterface("foo");
198 getContentViewCore().reload(true);
201 onPageFinishedHelper.waitForCallback(currentCallCount);
202 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo"));
206 @Feature({"AndroidWebView", "Android-JavaBridge"})
207 public void testTypeOfMethod() throws Throwable {
208 assertEquals("function",
209 executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
213 @Feature({"AndroidWebView", "Android-JavaBridge"})
214 public void testTypeOfInvalidMethod() throws Throwable {
215 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
219 @Feature({"AndroidWebView", "Android-JavaBridge"})
220 public void testCallingInvalidMethodRaisesException() throws Throwable {
221 assertRaisesException("testController.foo()");
225 @Feature({"AndroidWebView", "Android-JavaBridge"})
226 public void testUncaughtJavaExceptionRaisesJavaScriptException() throws Throwable {
227 injectObjectAndReload(new Object() {
228 public void method() { throw new RuntimeException("foo"); }
230 assertRaisesException("testObject.method()");
233 // Note that this requires that we can pass a JavaScript string to Java.
235 @Feature({"AndroidWebView", "Android-JavaBridge"})
236 public void testTypeOfStaticMethod() throws Throwable {
237 injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
238 executeJavaScript("testController.setStringValue(typeof testObject.staticMethod)");
239 assertEquals("function", mTestController.waitForStringValue());
242 // Note that this requires that we can pass a JavaScript string to Java.
244 @Feature({"AndroidWebView", "Android-JavaBridge"})
245 public void testCallStaticMethod() throws Throwable {
246 injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
247 executeJavaScript("testController.setStringValue(testObject.staticMethod())");
248 assertEquals("foo", mTestController.waitForStringValue());
252 @Feature({"AndroidWebView", "Android-JavaBridge"})
253 public void testPrivateMethodNotExposed() throws Throwable {
254 injectObjectAndReload(new Object() {
255 private void method() {}
256 protected void method2() {}
258 assertEquals("undefined",
259 executeJavaScriptAndGetStringResult("typeof testObject.method"));
260 assertEquals("undefined",
261 executeJavaScriptAndGetStringResult("typeof testObject.method2"));
265 @Feature({"AndroidWebView", "Android-JavaBridge"})
266 public void testReplaceInjectedObject() throws Throwable {
267 injectObjectAndReload(new Object() {
268 public void method() { mTestController.setStringValue("object 1"); }
270 executeJavaScript("testObject.method()");
271 assertEquals("object 1", mTestController.waitForStringValue());
273 injectObjectAndReload(new Object() {
274 public void method() { mTestController.setStringValue("object 2"); }
276 executeJavaScript("testObject.method()");
277 assertEquals("object 2", mTestController.waitForStringValue());
281 @Feature({"AndroidWebView", "Android-JavaBridge"})
282 public void testInjectNullObjectIsIgnored() throws Throwable {
283 injectObjectAndReload(null, "testObject");
284 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
288 @Feature({"AndroidWebView", "Android-JavaBridge"})
289 public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable {
290 injectObjectAndReload(new Object(), "testObject");
291 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
292 injectObjectAndReload(null, "testObject");
293 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
297 @Feature({"AndroidWebView", "Android-JavaBridge"})
298 public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable {
299 injectObjectAndReload(new Object() {
300 public void method() { mTestController.setStringValue("0 args"); }
301 public void method(int x) { mTestController.setStringValue("1 arg"); }
302 public void method(int x, int y) { mTestController.setStringValue("2 args"); }
304 executeJavaScript("testObject.method()");
305 assertEquals("0 args", mTestController.waitForStringValue());
306 executeJavaScript("testObject.method(42)");
307 assertEquals("1 arg", mTestController.waitForStringValue());
308 executeJavaScript("testObject.method(null)");
309 assertEquals("1 arg", mTestController.waitForStringValue());
310 executeJavaScript("testObject.method(undefined)");
311 assertEquals("1 arg", mTestController.waitForStringValue());
312 executeJavaScript("testObject.method(42, 42)");
313 assertEquals("2 args", mTestController.waitForStringValue());
317 @Feature({"AndroidWebView", "Android-JavaBridge"})
318 public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
319 assertRaisesException("testController.setIntValue()");
320 assertRaisesException("testController.setIntValue(42, 42)");
324 @Feature({"AndroidWebView", "Android-JavaBridge"})
325 public void testObjectPersistsAcrossPageLoads() throws Throwable {
326 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
327 synchronousPageReload();
328 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
332 @Feature({"AndroidWebView", "Android-JavaBridge"})
333 public void testCustomPropertiesCleanedUpOnPageReloads() throws Throwable {
334 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
335 executeJavaScript("testController.myProperty = 42;");
336 assertEquals("42", executeJavaScriptAndGetStringResult("testController.myProperty"));
337 synchronousPageReload();
338 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
339 assertEquals("undefined", executeJavaScriptAndGetStringResult("testController.myProperty"));
343 @Feature({"AndroidWebView", "Android-JavaBridge"})
344 public void testSameObjectInjectedMultipleTimes() throws Throwable {
346 private int mNumMethodInvocations;
347 public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
349 final TestObject testObject = new TestObject();
350 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
351 mTestCallbackHelperContainer.getOnPageFinishedHelper();
352 int currentCallCount = onPageFinishedHelper.getCallCount();
353 runTestOnUiThread(new Runnable() {
356 getContentViewCore().addPossiblyUnsafeJavascriptInterface(
357 testObject, "testObject1", null);
358 getContentViewCore().addPossiblyUnsafeJavascriptInterface(
359 testObject, "testObject2", null);
360 getContentViewCore().reload(true);
363 onPageFinishedHelper.waitForCallback(currentCallCount);
364 executeJavaScript("testObject1.method()");
365 assertEquals(1, mTestController.waitForIntValue());
366 executeJavaScript("testObject2.method()");
367 assertEquals(2, mTestController.waitForIntValue());
371 @Feature({"AndroidWebView", "Android-JavaBridge"})
372 public void testCallMethodOnReturnedObject() throws Throwable {
373 injectObjectAndReload(new Object() {
374 public Object getInnerObject() {
375 return new Object() {
376 public void method(int x) { mTestController.setIntValue(x); }
380 executeJavaScript("testObject.getInnerObject().method(42)");
381 assertEquals(42, mTestController.waitForIntValue());
385 @Feature({"AndroidWebView", "Android-JavaBridge"})
386 public void testReturnedObjectInjectedElsewhere() throws Throwable {
388 private int mNumMethodInvocations;
389 public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
391 final InnerObject innerObject = new InnerObject();
392 final Object object = new Object() {
393 public InnerObject getInnerObject() {
397 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
398 mTestCallbackHelperContainer.getOnPageFinishedHelper();
399 int currentCallCount = onPageFinishedHelper.getCallCount();
400 runTestOnUiThread(new Runnable() {
403 getContentViewCore().addPossiblyUnsafeJavascriptInterface(
404 object, "testObject", null);
405 getContentViewCore().addPossiblyUnsafeJavascriptInterface(
406 innerObject, "innerObject", null);
407 getContentViewCore().reload(true);
410 onPageFinishedHelper.waitForCallback(currentCallCount);
411 executeJavaScript("testObject.getInnerObject().method()");
412 assertEquals(1, mTestController.waitForIntValue());
413 executeJavaScript("innerObject.method()");
414 assertEquals(2, mTestController.waitForIntValue());
417 // Verify that Java objects returned from bridge object methods are dereferenced
418 // on the Java side once they have been fully dereferenced on the JS side.
419 // Failing this test would mean that methods returning objects effectively create a memory
422 @Feature({"AndroidWebView", "Android-JavaBridge"})
423 public void testReturnedObjectIsGarbageCollected() throws Throwable {
424 // Make sure V8 exposes "gc" property on the global object (enabled with --expose-gc flag)
425 assertEquals("function", executeJavaScriptAndGetStringResult("typeof gc"));
429 public InnerObject getInnerObject() {
430 InnerObject inner = new InnerObject();
431 mWeakRefForInner = new WeakReference<InnerObject>(inner);
434 // A weak reference is used to check InnerObject instance reachability.
435 WeakReference<InnerObject> mWeakRefForInner;
437 TestObject object = new TestObject();
438 injectObjectAndReload(object, "testObject");
439 // Initially, store a reference to the inner object in JS to make sure it's not
440 // garbage-collected prematurely.
441 assertEquals("object", executeJavaScriptAndGetStringResult(
443 "globalInner = testObject.getInnerObject(); return typeof globalInner; " +
445 assertTrue(object.mWeakRefForInner.get() != null);
446 // Check that returned Java object is being held by the Java bridge, thus it's not
447 // collected. Note that despite that what JavaDoc says about invoking "gc()", both Dalvik
448 // and ART actually run the collector.
449 Runtime.getRuntime().gc();
450 assertTrue(object.mWeakRefForInner.get() != null);
451 // Now dereference the inner object in JS and run GC to collect the interface object.
452 assertEquals("true", executeJavaScriptAndGetStringResult(
454 "delete globalInner; gc(); return (typeof globalInner == 'undefined'); " +
456 // Force GC on the Java side again. The bridge had to release the inner object, so it must
457 // be collected this time.
458 Runtime.getRuntime().gc();
459 assertEquals(null, object.mWeakRefForInner.get());
463 @Feature({"AndroidWebView", "Android-JavaBridge"})
464 public void testSameReturnedObjectUsesSameWrapper() throws Throwable {
467 final InnerObject innerObject = new InnerObject();
468 final Object injectedTestObject = new Object() {
469 public InnerObject getInnerObject() {
473 injectObjectAndReload(injectedTestObject, "injectedTestObject");
474 executeJavaScript("inner1 = injectedTestObject.getInnerObject()");
475 executeJavaScript("inner2 = injectedTestObject.getInnerObject()");
476 assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner1"));
477 assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner2"));
478 assertEquals("true", executeJavaScriptAndGetStringResult("inner1 === inner2"));
482 @Feature({"AndroidWebView", "Android-JavaBridge"})
483 public void testMethodInvokedOnBackgroundThread() throws Throwable {
484 injectObjectAndReload(new Object() {
485 public void captureThreadId() {
486 mTestController.setLongValue(Thread.currentThread().getId());
489 executeJavaScript("testObject.captureThreadId()");
490 final long threadId = mTestController.waitForLongValue();
491 assertFalse(threadId == Thread.currentThread().getId());
492 runTestOnUiThread(new Runnable() {
495 assertFalse(threadId == Thread.currentThread().getId());
501 @Feature({"AndroidWebView", "Android-JavaBridge"})
502 public void testPublicInheritedMethod() throws Throwable {
504 public void method(int x) { mTestController.setIntValue(x); }
506 class Derived extends Base {
508 injectObjectAndReload(new Derived(), "testObject");
509 assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method"));
510 executeJavaScript("testObject.method(42)");
511 assertEquals(42, mTestController.waitForIntValue());
515 @Feature({"AndroidWebView", "Android-JavaBridge"})
516 public void testPrivateInheritedMethod() throws Throwable {
518 private void method() {}
520 class Derived extends Base {
522 injectObjectAndReload(new Derived(), "testObject");
523 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method"));
527 @Feature({"AndroidWebView", "Android-JavaBridge"})
528 public void testOverriddenMethod() throws Throwable {
530 public void method() { mTestController.setStringValue("base"); }
532 class Derived extends Base {
534 public void method() { mTestController.setStringValue("derived"); }
536 injectObjectAndReload(new Derived(), "testObject");
537 executeJavaScript("testObject.method()");
538 assertEquals("derived", mTestController.waitForStringValue());
542 @Feature({"AndroidWebView", "Android-JavaBridge"})
543 public void testEnumerateMembers() throws Throwable {
544 injectObjectAndReload(new Object() {
545 public void method() {}
546 private void privateMethod() {}
548 private int privateField;
551 "var result = \"\"; " +
552 "for (x in testObject) { result += \" \" + x } " +
553 "testController.setStringValue(result);");
554 assertEquals(" equals getClass hashCode method notify notifyAll toString wait",
555 mTestController.waitForStringValue());
559 @Feature({"AndroidWebView", "Android-JavaBridge"})
560 public void testReflectPublicMethod() throws Throwable {
561 injectObjectAndReload(new Object() {
562 public Class<?> myGetClass() { return getClass(); }
563 public String method() { return "foo"; }
565 assertEquals("foo", executeJavaScriptAndGetStringResult(
566 "testObject.myGetClass().getMethod('method', null).invoke(testObject, null)" +
571 @Feature({"AndroidWebView", "Android-JavaBridge"})
572 public void testReflectPublicField() throws Throwable {
573 injectObjectAndReload(new Object() {
574 public Class<?> myGetClass() { return getClass(); }
575 public String field = "foo";
577 assertEquals("foo", executeJavaScriptAndGetStringResult(
578 "testObject.myGetClass().getField('field').get(testObject).toString()"));
582 @Feature({"AndroidWebView", "Android-JavaBridge"})
583 public void testReflectPrivateMethodRaisesException() throws Throwable {
584 injectObjectAndReload(new Object() {
585 public Class<?> myGetClass() { return getClass(); }
586 private void method() {};
588 assertRaisesException("testObject.myGetClass().getMethod('method', null)");
589 // getDeclaredMethod() is able to access a private method, but invoke()
590 // throws a Java exception.
591 assertRaisesException(
592 "testObject.myGetClass().getDeclaredMethod('method', null)." +
593 "invoke(testObject, null)");
597 @Feature({"AndroidWebView", "Android-JavaBridge"})
598 public void testReflectPrivateFieldRaisesException() throws Throwable {
599 injectObjectAndReload(new Object() {
600 public Class<?> myGetClass() { return getClass(); }
603 assertRaisesException("testObject.myGetClass().getField('field')");
604 // getDeclaredField() is able to access a private field, but getInt()
605 // throws a Java exception.
606 assertRaisesException(
607 "testObject.myGetClass().getDeclaredField('field').getInt(testObject)");
611 @Feature({"AndroidWebView", "Android-JavaBridge"})
612 public void testAllowNonAnnotatedMethods() throws Throwable {
613 injectObjectAndReload(new Object() {
614 public String allowed() { return "foo"; }
615 }, "testObject", null);
617 // Test calling a method of an explicitly inherited class (Base#allowed()).
618 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
620 // Test calling a method of an implicitly inherited class (Object#toString()).
621 assertEquals("string", executeJavaScriptAndGetStringResult("typeof testObject.toString()"));
625 @Feature({"AndroidWebView", "Android-JavaBridge"})
626 public void testAllowOnlyAnnotatedMethods() throws Throwable {
627 injectObjectAndReload(new Object() {
629 public String allowed() { return "foo"; }
631 public String disallowed() { return "bar"; }
632 }, "testObject", JavascriptInterface.class);
634 // getClass() is an Object method and does not have the @JavascriptInterface annotation and
635 // should not be able to be called.
636 assertRaisesException("testObject.getClass()");
637 assertEquals("undefined", executeJavaScriptAndGetStringResult(
638 "typeof testObject.getClass"));
640 // allowed() is marked with the @JavascriptInterface annotation and should be allowed to be
642 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
644 // disallowed() is not marked with the @JavascriptInterface annotation and should not be
645 // able to be called.
646 assertRaisesException("testObject.disallowed()");
647 assertEquals("undefined", executeJavaScriptAndGetStringResult(
648 "typeof testObject.disallowed"));
652 @Feature({"AndroidWebView", "Android-JavaBridge"})
653 public void testAnnotationRequirementRetainsPropertyAcrossObjects() throws Throwable {
656 public String safe() { return "foo"; }
658 public String unsafe() { return "bar"; }
663 public Test getTest() { return new Test(); }
666 // First test with safe mode off.
667 injectObjectAndReload(new TestReturner(), "unsafeTestObject", null);
669 // safe() should be able to be called regardless of whether or not we are in safe mode.
670 assertEquals("foo", executeJavaScriptAndGetStringResult(
671 "unsafeTestObject.getTest().safe()"));
672 // unsafe() should be able to be called because we are not in safe mode.
673 assertEquals("bar", executeJavaScriptAndGetStringResult(
674 "unsafeTestObject.getTest().unsafe()"));
676 // Now test with safe mode on.
677 injectObjectAndReload(new TestReturner(), "safeTestObject", JavascriptInterface.class);
679 // safe() should be able to be called regardless of whether or not we are in safe mode.
680 assertEquals("foo", executeJavaScriptAndGetStringResult(
681 "safeTestObject.getTest().safe()"));
682 // unsafe() should not be able to be called because we are in safe mode.
683 assertRaisesException("safeTestObject.getTest().unsafe()");
684 assertEquals("undefined", executeJavaScriptAndGetStringResult(
685 "typeof safeTestObject.getTest().unsafe"));
686 // getClass() is an Object method and does not have the @JavascriptInterface annotation and
687 // should not be able to be called.
688 assertRaisesException("safeTestObject.getTest().getClass()");
689 assertEquals("undefined", executeJavaScriptAndGetStringResult(
690 "typeof safeTestObject.getTest().getClass"));
694 @Feature({"AndroidWebView", "Android-JavaBridge"})
695 public void testAnnotationDoesNotGetInherited() throws Throwable {
698 public void base() { }
701 class Child extends Base {
703 public void base() { }
706 injectObjectAndReload(new Child(), "testObject", JavascriptInterface.class);
708 // base() is inherited. The inherited method does not have the @JavascriptInterface
709 // annotation and should not be able to be called.
710 assertRaisesException("testObject.base()");
711 assertEquals("undefined", executeJavaScriptAndGetStringResult(
712 "typeof testObject.base"));
715 @SuppressWarnings("javadoc")
716 @Retention(RetentionPolicy.RUNTIME)
717 @Target({ElementType.METHOD})
718 @interface TestAnnotation {
722 @Feature({"AndroidWebView", "Android-JavaBridge"})
723 public void testCustomAnnotationRestriction() throws Throwable {
726 public String checkTestAnnotationFoo() { return "bar"; }
729 public String checkJavascriptInterfaceFoo() { return "bar"; }
732 // Inject javascriptInterfaceObj and require the JavascriptInterface annotation.
733 injectObjectAndReload(new Test(), "javascriptInterfaceObj", JavascriptInterface.class);
735 // Test#testAnnotationFoo() should fail, as it isn't annotated with JavascriptInterface.
736 assertRaisesException("javascriptInterfaceObj.checkTestAnnotationFoo()");
737 assertEquals("undefined", executeJavaScriptAndGetStringResult(
738 "typeof javascriptInterfaceObj.checkTestAnnotationFoo"));
740 // Test#javascriptInterfaceFoo() should pass, as it is annotated with JavascriptInterface.
741 assertEquals("bar", executeJavaScriptAndGetStringResult(
742 "javascriptInterfaceObj.checkJavascriptInterfaceFoo()"));
744 // Inject testAnnotationObj and require the TestAnnotation annotation.
745 injectObjectAndReload(new Test(), "testAnnotationObj", TestAnnotation.class);
747 // Test#testAnnotationFoo() should pass, as it is annotated with TestAnnotation.
748 assertEquals("bar", executeJavaScriptAndGetStringResult(
749 "testAnnotationObj.checkTestAnnotationFoo()"));
751 // Test#javascriptInterfaceFoo() should fail, as it isn't annotated with TestAnnotation.
752 assertRaisesException("testAnnotationObj.checkJavascriptInterfaceFoo()");
753 assertEquals("undefined", executeJavaScriptAndGetStringResult(
754 "typeof testAnnotationObj.checkJavascriptInterfaceFoo"));
758 @Feature({"AndroidWebView", "Android-JavaBridge"})
759 public void testAddJavascriptInterfaceIsSafeByDefault() throws Throwable {
761 public String blocked() { return "bar"; }
764 public String allowed() { return "bar"; }
767 // Manually inject the Test object, making sure to use the
768 // ContentViewCore#addJavascriptInterface, not the possibly unsafe version.
769 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
770 mTestCallbackHelperContainer.getOnPageFinishedHelper();
771 int currentCallCount = onPageFinishedHelper.getCallCount();
772 runTestOnUiThread(new Runnable() {
775 getContentViewCore().addJavascriptInterface(new Test(),
777 getContentViewCore().reload(true);
780 onPageFinishedHelper.waitForCallback(currentCallCount);
782 // Test#allowed() should pass, as it is annotated with JavascriptInterface.
783 assertEquals("bar", executeJavaScriptAndGetStringResult(
784 "testObject.allowed()"));
786 // Test#blocked() should fail, as it isn't annotated with JavascriptInterface.
787 assertRaisesException("testObject.blocked()");
788 assertEquals("undefined", executeJavaScriptAndGetStringResult(
789 "typeof testObject.blocked"));
793 @Feature({"AndroidWebView", "Android-JavaBridge"})
794 public void testObjectsInspection() throws Throwable {
797 public String m1() { return "foo"; }
800 public String m2() { return "bar"; }
803 public String m2(int x) { return "bar " + x; }
806 final String jsObjectKeysTestTemplate = "Object.keys(%s).toString()";
807 final String jsForInTestTemplate =
809 " var s=[]; for(var m in %s) s.push(m); return s.join(\",\")" +
811 final String inspectableObjectName = "testObj1";
812 final String nonInspectableObjectName = "testObj2";
814 // Inspection is enabled by default.
815 injectObjectAndReload(new Test(), inspectableObjectName, JavascriptInterface.class);
817 assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
818 String.format(jsObjectKeysTestTemplate, inspectableObjectName)));
819 assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
820 String.format(jsForInTestTemplate, inspectableObjectName)));
822 runTestOnUiThread(new Runnable() {
825 getContentViewCore().setAllowJavascriptInterfacesInspection(false);
829 injectObjectAndReload(new Test(), nonInspectableObjectName, JavascriptInterface.class);
831 assertEquals("", executeJavaScriptAndGetStringResult(
832 String.format(jsObjectKeysTestTemplate, nonInspectableObjectName)));
833 assertEquals("", executeJavaScriptAndGetStringResult(
834 String.format(jsForInTestTemplate, nonInspectableObjectName)));
838 @Feature({"AndroidWebView", "Android-JavaBridge"})
839 public void testAccessToObjectGetClassIsBlocked() throws Throwable {
840 injectObjectAndReload(new Object(), "testObject");
841 assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.getClass"));
842 assertRaisesException("testObject.getClass()");