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.DisabledTest;
10 import org.chromium.base.test.util.Feature;
11 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
12 import org.chromium.content_shell_apk.ContentShellActivity;
14 import java.lang.annotation.Annotation;
15 import java.lang.annotation.ElementType;
16 import java.lang.annotation.Retention;
17 import java.lang.annotation.RetentionPolicy;
18 import java.lang.annotation.Target;
19 import java.lang.ref.WeakReference;
22 * Part of the test suite for the Java Bridge. Tests a number of features including ...
23 * - The type of injected objects
24 * - The type of their methods
28 * - Calling methods on returned objects
29 * - Multiply injected objects
33 public class JavaBridgeBasicsTest extends JavaBridgeTestBase {
34 private class TestController extends Controller {
35 private int mIntValue;
36 private long mLongValue;
37 private String mStringValue;
38 private boolean mBooleanValue;
40 public synchronized void setIntValue(int x) {
42 notifyResultIsReady();
44 public synchronized void setLongValue(long x) {
46 notifyResultIsReady();
48 public synchronized void setStringValue(String x) {
50 notifyResultIsReady();
52 public synchronized void setBooleanValue(boolean x) {
54 notifyResultIsReady();
57 public synchronized int waitForIntValue() {
61 public synchronized long waitForLongValue() {
65 public synchronized String waitForStringValue() {
69 public synchronized boolean waitForBooleanValue() {
75 private static class ObjectWithStaticMethod {
76 public static String staticMethod() {
81 TestController mTestController;
84 protected void setUp() throws Exception {
86 mTestController = new TestController();
87 setUpContentView(mTestController, "testController");
91 protected ContentShellActivity launchContentShellWithUrl(String url) {
92 // Expose a global function "gc()" into pages.
93 return launchContentShellWithUrlAndCommandLineArgs(
94 url, new String[]{ "--js-flags=--expose-gc" });
97 // Note that this requires that we can pass a JavaScript string to Java.
98 protected String executeJavaScriptAndGetStringResult(String script) throws Throwable {
99 executeJavaScript("testController.setStringValue(" + script + ");");
100 return mTestController.waitForStringValue();
103 protected void injectObjectAndReload(final Object object, final String name) throws Throwable {
104 injectObjectAndReload(object, name, null);
107 protected void injectObjectAndReload(final Object object, final String name,
108 final Class<? extends Annotation> requiredAnnotation) throws Throwable {
109 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
110 mTestCallbackHelperContainer.getOnPageFinishedHelper();
111 int currentCallCount = onPageFinishedHelper.getCallCount();
112 runTestOnUiThread(new Runnable() {
115 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(object,
116 name, requiredAnnotation);
117 getContentView().getContentViewCore().reload(true);
120 onPageFinishedHelper.waitForCallback(currentCallCount);
123 protected void synchronousPageReload() throws Throwable {
124 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
125 mTestCallbackHelperContainer.getOnPageFinishedHelper();
126 int currentCallCount = onPageFinishedHelper.getCallCount();
127 runTestOnUiThread(new Runnable() {
130 getContentView().getContentViewCore().reload(true);
133 onPageFinishedHelper.waitForCallback(currentCallCount);
136 // Note that this requires that we can pass a JavaScript boolean to Java.
137 private void assertRaisesException(String script) throws Throwable {
138 executeJavaScript("try {" +
140 " testController.setBooleanValue(false);" +
141 "} catch (exception) {" +
142 " testController.setBooleanValue(true);" +
144 assertTrue(mTestController.waitForBooleanValue());
148 @Feature({"AndroidWebView", "Android-JavaBridge"})
149 public void testTypeOfInjectedObject() throws Throwable {
150 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
154 @Feature({"AndroidWebView", "Android-JavaBridge"})
155 public void testAdditionNotReflectedUntilReload() throws Throwable {
156 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
157 runTestOnUiThread(new Runnable() {
160 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
161 new Object(), "testObject", null);
164 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
165 synchronousPageReload();
166 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
170 @Feature({"AndroidWebView", "Android-JavaBridge"})
171 public void testRemovalNotReflectedUntilReload() throws Throwable {
172 injectObjectAndReload(new Object(), "testObject");
173 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
174 runTestOnUiThread(new Runnable() {
177 getContentView().getContentViewCore().removeJavascriptInterface("testObject");
180 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
181 synchronousPageReload();
182 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
186 @Feature({"AndroidWebView", "Android-JavaBridge"})
187 public void testRemoveObjectNotAdded() throws Throwable {
188 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
189 mTestCallbackHelperContainer.getOnPageFinishedHelper();
190 int currentCallCount = onPageFinishedHelper.getCallCount();
191 runTestOnUiThread(new Runnable() {
194 getContentView().getContentViewCore().removeJavascriptInterface("foo");
195 getContentView().getContentViewCore().reload(true);
198 onPageFinishedHelper.waitForCallback(currentCallCount);
199 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo"));
203 @Feature({"AndroidWebView", "Android-JavaBridge"})
204 public void testTypeOfMethod() throws Throwable {
205 assertEquals("function",
206 executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
210 @Feature({"AndroidWebView", "Android-JavaBridge"})
211 public void testTypeOfInvalidMethod() throws Throwable {
212 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
216 @Feature({"AndroidWebView", "Android-JavaBridge"})
217 public void testCallingInvalidMethodRaisesException() throws Throwable {
218 assertRaisesException("testController.foo()");
222 @Feature({"AndroidWebView", "Android-JavaBridge"})
223 public void testUncaughtJavaExceptionRaisesJavaScriptException() throws Throwable {
224 injectObjectAndReload(new Object() {
225 public void method() { throw new RuntimeException("foo"); }
227 assertRaisesException("testObject.method()");
230 // Note that this requires that we can pass a JavaScript string to Java.
232 @Feature({"AndroidWebView", "Android-JavaBridge"})
233 public void testTypeOfStaticMethod() throws Throwable {
234 injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
235 executeJavaScript("testController.setStringValue(typeof testObject.staticMethod)");
236 assertEquals("function", mTestController.waitForStringValue());
239 // Note that this requires that we can pass a JavaScript string to Java.
241 @Feature({"AndroidWebView", "Android-JavaBridge"})
242 public void testCallStaticMethod() throws Throwable {
243 injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
244 executeJavaScript("testController.setStringValue(testObject.staticMethod())");
245 assertEquals("foo", mTestController.waitForStringValue());
249 @Feature({"AndroidWebView", "Android-JavaBridge"})
250 public void testPrivateMethodNotExposed() throws Throwable {
251 injectObjectAndReload(new Object() {
252 private void method() {}
253 protected void method2() {}
255 assertEquals("undefined",
256 executeJavaScriptAndGetStringResult("typeof testObject.method"));
257 assertEquals("undefined",
258 executeJavaScriptAndGetStringResult("typeof testObject.method2"));
262 @Feature({"AndroidWebView", "Android-JavaBridge"})
263 public void testReplaceInjectedObject() throws Throwable {
264 injectObjectAndReload(new Object() {
265 public void method() { mTestController.setStringValue("object 1"); }
267 executeJavaScript("testObject.method()");
268 assertEquals("object 1", mTestController.waitForStringValue());
270 injectObjectAndReload(new Object() {
271 public void method() { mTestController.setStringValue("object 2"); }
273 executeJavaScript("testObject.method()");
274 assertEquals("object 2", mTestController.waitForStringValue());
278 @Feature({"AndroidWebView", "Android-JavaBridge"})
279 public void testInjectNullObjectIsIgnored() throws Throwable {
280 injectObjectAndReload(null, "testObject");
281 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
285 @Feature({"AndroidWebView", "Android-JavaBridge"})
286 public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable {
287 injectObjectAndReload(new Object(), "testObject");
288 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
289 injectObjectAndReload(null, "testObject");
290 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
294 @Feature({"AndroidWebView", "Android-JavaBridge"})
295 public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable {
296 injectObjectAndReload(new Object() {
297 public void method() { mTestController.setStringValue("0 args"); }
298 public void method(int x) { mTestController.setStringValue("1 arg"); }
299 public void method(int x, int y) { mTestController.setStringValue("2 args"); }
301 executeJavaScript("testObject.method()");
302 assertEquals("0 args", mTestController.waitForStringValue());
303 executeJavaScript("testObject.method(42)");
304 assertEquals("1 arg", mTestController.waitForStringValue());
305 executeJavaScript("testObject.method(null)");
306 assertEquals("1 arg", mTestController.waitForStringValue());
307 executeJavaScript("testObject.method(undefined)");
308 assertEquals("1 arg", mTestController.waitForStringValue());
309 executeJavaScript("testObject.method(42, 42)");
310 assertEquals("2 args", mTestController.waitForStringValue());
314 @Feature({"AndroidWebView", "Android-JavaBridge"})
315 public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
316 assertRaisesException("testController.setIntValue()");
317 assertRaisesException("testController.setIntValue(42, 42)");
321 @Feature({"AndroidWebView", "Android-JavaBridge"})
322 public void testObjectPersistsAcrossPageLoads() throws Throwable {
323 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
324 synchronousPageReload();
325 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
329 @Feature({"AndroidWebView", "Android-JavaBridge"})
330 public void testClientPropertiesPersistAcrossPageLoads() throws Throwable {
331 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
332 executeJavaScript("testController.myProperty = 42;");
333 assertEquals("42", executeJavaScriptAndGetStringResult("testController.myProperty"));
334 synchronousPageReload();
335 assertEquals("42", executeJavaScriptAndGetStringResult("testController.myProperty"));
339 @Feature({"AndroidWebView", "Android-JavaBridge"})
340 public void testSameObjectInjectedMultipleTimes() throws Throwable {
342 private int mNumMethodInvocations;
343 public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
345 final TestObject testObject = new TestObject();
346 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
347 mTestCallbackHelperContainer.getOnPageFinishedHelper();
348 int currentCallCount = onPageFinishedHelper.getCallCount();
349 runTestOnUiThread(new Runnable() {
352 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
353 testObject, "testObject1", null);
354 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
355 testObject, "testObject2", null);
356 getContentView().getContentViewCore().reload(true);
359 onPageFinishedHelper.waitForCallback(currentCallCount);
360 executeJavaScript("testObject1.method()");
361 assertEquals(1, mTestController.waitForIntValue());
362 executeJavaScript("testObject2.method()");
363 assertEquals(2, mTestController.waitForIntValue());
367 @Feature({"AndroidWebView", "Android-JavaBridge"})
368 public void testCallMethodOnReturnedObject() throws Throwable {
369 injectObjectAndReload(new Object() {
370 public Object getInnerObject() {
371 return new Object() {
372 public void method(int x) { mTestController.setIntValue(x); }
376 executeJavaScript("testObject.getInnerObject().method(42)");
377 assertEquals(42, mTestController.waitForIntValue());
381 @Feature({"AndroidWebView", "Android-JavaBridge"})
382 public void testReturnedObjectInjectedElsewhere() throws Throwable {
384 private int mNumMethodInvocations;
385 public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
387 final InnerObject innerObject = new InnerObject();
388 final Object object = new Object() {
389 public InnerObject getInnerObject() {
393 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
394 mTestCallbackHelperContainer.getOnPageFinishedHelper();
395 int currentCallCount = onPageFinishedHelper.getCallCount();
396 runTestOnUiThread(new Runnable() {
399 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
400 object, "testObject", null);
401 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
402 innerObject, "innerObject", null);
403 getContentView().getContentViewCore().reload(true);
406 onPageFinishedHelper.waitForCallback(currentCallCount);
407 executeJavaScript("testObject.getInnerObject().method()");
408 assertEquals(1, mTestController.waitForIntValue());
409 executeJavaScript("innerObject.method()");
410 assertEquals(2, mTestController.waitForIntValue());
413 // Verify that Java objects returned from bridge object methods are dereferenced
414 // on the Java side once they have been fully dereferenced on the JS side.
415 // Failing this test would mean that methods returning objects effectively create a memory
418 @Feature({"AndroidWebView", "Android-JavaBridge"})
419 public void testReturnedObjectIsGarbageCollected() throws Throwable {
420 // Make sure V8 exposes "gc" property on the global object (enabled with --expose-gc flag)
421 assertEquals("function", executeJavaScriptAndGetStringResult("typeof gc"));
425 public InnerObject getInnerObject() {
426 InnerObject inner = new InnerObject();
427 weakRefForInner = new WeakReference<InnerObject>(inner);
430 // A weak reference is used to check InnerObject instance reachability.
431 WeakReference<InnerObject> weakRefForInner;
433 TestObject object = new TestObject();
434 injectObjectAndReload(object, "testObject");
435 // Initially, store a reference to the inner object in JS to make sure it's not
436 // garbage-collected prematurely.
437 assertEquals("object", executeJavaScriptAndGetStringResult(
439 "globalInner = testObject.getInnerObject(); return typeof globalInner; " +
441 assertTrue(object.weakRefForInner.get() != null);
442 // Check that returned Java object is being held by the Java bridge, thus it's not
443 // collected. Note that despite that what JavaDoc says about invoking "gc()", both Dalvik
444 // and ART actually run the collector.
445 Runtime.getRuntime().gc();
446 assertTrue(object.weakRefForInner.get() != null);
447 // Now dereference the inner object in JS and run GC to collect the interface object.
448 assertEquals("true", executeJavaScriptAndGetStringResult(
450 "delete globalInner; gc(); return (typeof globalInner == 'undefined'); " +
452 // Force GC on the Java side again. The bridge had to release the inner object, so it must
453 // be collected this time.
454 Runtime.getRuntime().gc();
455 assertEquals(null, object.weakRefForInner.get());
459 * The current Java bridge implementation doesn't reuse JS wrappers when returning
460 * the same object from a method. That looks wrong. For example, in the case of DOM,
461 * wrappers are reused, which allows JS code to attach custom properties to interface
462 * objects and use them regardless of the way the reference has been obtained:
463 * via copying a JS reference or by calling the method one more time (assuming that
464 * the method is supposed to return a reference to the same object each time).
465 * TODO(mnaganov): Fix this in the new implementation.
468 * @Feature({"AndroidWebView", "Android-JavaBridge"})
471 public void testSameReturnedObjectUsesSameWrapper() throws Throwable {
474 final InnerObject innerObject = new InnerObject();
475 final Object injectedTestObject = new Object() {
476 public InnerObject getInnerObject() {
480 injectObjectAndReload(injectedTestObject, "injectedTestObject");
481 executeJavaScript("inner1 = injectedTestObject.getInnerObject()");
482 executeJavaScript("inner2 = injectedTestObject.getInnerObject()");
483 assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner1"));
484 assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner2"));
485 assertEquals("true", executeJavaScriptAndGetStringResult("inner1 === inner2"));
489 @Feature({"AndroidWebView", "Android-JavaBridge"})
490 public void testMethodInvokedOnBackgroundThread() throws Throwable {
491 injectObjectAndReload(new Object() {
492 public void captureThreadId() {
493 mTestController.setLongValue(Thread.currentThread().getId());
496 executeJavaScript("testObject.captureThreadId()");
497 final long threadId = mTestController.waitForLongValue();
498 assertFalse(threadId == Thread.currentThread().getId());
499 runTestOnUiThread(new Runnable() {
502 assertFalse(threadId == Thread.currentThread().getId());
508 @Feature({"AndroidWebView", "Android-JavaBridge"})
509 public void testPublicInheritedMethod() throws Throwable {
511 public void method(int x) { mTestController.setIntValue(x); }
513 class Derived extends Base {
515 injectObjectAndReload(new Derived(), "testObject");
516 assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method"));
517 executeJavaScript("testObject.method(42)");
518 assertEquals(42, mTestController.waitForIntValue());
522 @Feature({"AndroidWebView", "Android-JavaBridge"})
523 public void testPrivateInheritedMethod() throws Throwable {
525 private void method() {}
527 class Derived extends Base {
529 injectObjectAndReload(new Derived(), "testObject");
530 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method"));
534 @Feature({"AndroidWebView", "Android-JavaBridge"})
535 public void testOverriddenMethod() throws Throwable {
537 public void method() { mTestController.setStringValue("base"); }
539 class Derived extends Base {
541 public void method() { mTestController.setStringValue("derived"); }
543 injectObjectAndReload(new Derived(), "testObject");
544 executeJavaScript("testObject.method()");
545 assertEquals("derived", mTestController.waitForStringValue());
549 @Feature({"AndroidWebView", "Android-JavaBridge"})
550 public void testEnumerateMembers() throws Throwable {
551 injectObjectAndReload(new Object() {
552 public void method() {}
553 private void privateMethod() {}
555 private int privateField;
558 "var result = \"\"; " +
559 "for (x in testObject) { result += \" \" + x } " +
560 "testController.setStringValue(result);");
561 assertEquals(" equals getClass hashCode method notify notifyAll toString wait",
562 mTestController.waitForStringValue());
566 @Feature({"AndroidWebView", "Android-JavaBridge"})
567 public void testReflectPublicMethod() throws Throwable {
568 injectObjectAndReload(new Object() {
569 public String method() { return "foo"; }
571 assertEquals("foo", executeJavaScriptAndGetStringResult(
572 "testObject.getClass().getMethod('method', null).invoke(testObject, null)" +
577 @Feature({"AndroidWebView", "Android-JavaBridge"})
578 public void testReflectPublicField() throws Throwable {
579 injectObjectAndReload(new Object() {
580 public String field = "foo";
582 assertEquals("foo", executeJavaScriptAndGetStringResult(
583 "testObject.getClass().getField('field').get(testObject).toString()"));
587 @Feature({"AndroidWebView", "Android-JavaBridge"})
588 public void testReflectPrivateMethodRaisesException() throws Throwable {
589 injectObjectAndReload(new Object() {
590 private void method() {};
592 assertRaisesException("testObject.getClass().getMethod('method', null)");
593 // getDeclaredMethod() is able to access a private method, but invoke()
594 // throws a Java exception.
595 assertRaisesException(
596 "testObject.getClass().getDeclaredMethod('method', null).invoke(testObject, null)");
600 @Feature({"AndroidWebView", "Android-JavaBridge"})
601 public void testReflectPrivateFieldRaisesException() throws Throwable {
602 injectObjectAndReload(new Object() {
605 assertRaisesException("testObject.getClass().getField('field')");
606 // getDeclaredField() is able to access a private field, but getInt()
607 // throws a Java exception.
608 assertRaisesException(
609 "testObject.getClass().getDeclaredField('field').getInt(testObject)");
613 @Feature({"AndroidWebView", "Android-JavaBridge"})
614 public void testAllowNonAnnotatedMethods() throws Throwable {
615 injectObjectAndReload(new Object() {
616 public String allowed() { return "foo"; }
617 }, "testObject", null);
619 // Test calling a method of an explicitly inherited class (Base#allowed()).
620 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
622 // Test calling a method of an implicitly inherited class (Object#getClass()).
623 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject.getClass()"));
627 @Feature({"AndroidWebView", "Android-JavaBridge"})
628 public void testAllowOnlyAnnotatedMethods() throws Throwable {
629 injectObjectAndReload(new Object() {
631 public String allowed() { return "foo"; }
633 public String disallowed() { return "bar"; }
634 }, "testObject", JavascriptInterface.class);
636 // getClass() is an Object method and does not have the @JavascriptInterface annotation and
637 // should not be able to be called.
638 assertRaisesException("testObject.getClass()");
639 assertEquals("undefined", executeJavaScriptAndGetStringResult(
640 "typeof testObject.getClass"));
642 // allowed() is marked with the @JavascriptInterface annotation and should be allowed to be
644 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
646 // disallowed() is not marked with the @JavascriptInterface annotation and should not be
647 // able to be called.
648 assertRaisesException("testObject.disallowed()");
649 assertEquals("undefined", executeJavaScriptAndGetStringResult(
650 "typeof testObject.disallowed"));
654 @Feature({"AndroidWebView", "Android-JavaBridge"})
655 public void testAnnotationRequirementRetainsPropertyAcrossObjects() throws Throwable {
658 public String safe() { return "foo"; }
660 public String unsafe() { return "bar"; }
665 public Test getTest() { return new Test(); }
668 // First test with safe mode off.
669 injectObjectAndReload(new TestReturner(), "unsafeTestObject", null);
671 // safe() should be able to be called regardless of whether or not we are in safe mode.
672 assertEquals("foo", executeJavaScriptAndGetStringResult(
673 "unsafeTestObject.getTest().safe()"));
674 // unsafe() should be able to be called because we are not in safe mode.
675 assertEquals("bar", executeJavaScriptAndGetStringResult(
676 "unsafeTestObject.getTest().unsafe()"));
678 // Now test with safe mode on.
679 injectObjectAndReload(new TestReturner(), "safeTestObject", JavascriptInterface.class);
681 // safe() should be able to be called regardless of whether or not we are in safe mode.
682 assertEquals("foo", executeJavaScriptAndGetStringResult(
683 "safeTestObject.getTest().safe()"));
684 // unsafe() should not be able to be called because we are in safe mode.
685 assertRaisesException("safeTestObject.getTest().unsafe()");
686 assertEquals("undefined", executeJavaScriptAndGetStringResult(
687 "typeof safeTestObject.getTest().unsafe"));
688 // getClass() is an Object method and does not have the @JavascriptInterface annotation and
689 // should not be able to be called.
690 assertRaisesException("safeTestObject.getTest().getClass()");
691 assertEquals("undefined", executeJavaScriptAndGetStringResult(
692 "typeof safeTestObject.getTest().getClass"));
696 @Feature({"AndroidWebView", "Android-JavaBridge"})
697 public void testAnnotationDoesNotGetInherited() throws Throwable {
700 public void base() { }
703 class Child extends Base {
705 public void base() { }
708 injectObjectAndReload(new Child(), "testObject", JavascriptInterface.class);
710 // base() is inherited. The inherited method does not have the @JavascriptInterface
711 // annotation and should not be able to be called.
712 assertRaisesException("testObject.base()");
713 assertEquals("undefined", executeJavaScriptAndGetStringResult(
714 "typeof testObject.base"));
717 @SuppressWarnings("javadoc")
718 @Retention(RetentionPolicy.RUNTIME)
719 @Target({ElementType.METHOD})
720 @interface TestAnnotation {
724 @Feature({"AndroidWebView", "Android-JavaBridge"})
725 public void testCustomAnnotationRestriction() throws Throwable {
728 public String checkTestAnnotationFoo() { return "bar"; }
731 public String checkJavascriptInterfaceFoo() { return "bar"; }
734 // Inject javascriptInterfaceObj and require the JavascriptInterface annotation.
735 injectObjectAndReload(new Test(), "javascriptInterfaceObj", JavascriptInterface.class);
737 // Test#testAnnotationFoo() should fail, as it isn't annotated with JavascriptInterface.
738 assertRaisesException("javascriptInterfaceObj.checkTestAnnotationFoo()");
739 assertEquals("undefined", executeJavaScriptAndGetStringResult(
740 "typeof javascriptInterfaceObj.checkTestAnnotationFoo"));
742 // Test#javascriptInterfaceFoo() should pass, as it is annotated with JavascriptInterface.
743 assertEquals("bar", executeJavaScriptAndGetStringResult(
744 "javascriptInterfaceObj.checkJavascriptInterfaceFoo()"));
746 // Inject testAnnotationObj and require the TestAnnotation annotation.
747 injectObjectAndReload(new Test(), "testAnnotationObj", TestAnnotation.class);
749 // Test#testAnnotationFoo() should pass, as it is annotated with TestAnnotation.
750 assertEquals("bar", executeJavaScriptAndGetStringResult(
751 "testAnnotationObj.checkTestAnnotationFoo()"));
753 // Test#javascriptInterfaceFoo() should fail, as it isn't annotated with TestAnnotation.
754 assertRaisesException("testAnnotationObj.checkJavascriptInterfaceFoo()");
755 assertEquals("undefined", executeJavaScriptAndGetStringResult(
756 "typeof testAnnotationObj.checkJavascriptInterfaceFoo"));
760 @Feature({"AndroidWebView", "Android-JavaBridge"})
761 public void testAddJavascriptInterfaceIsSafeByDefault() throws Throwable {
763 public String blocked() { return "bar"; }
766 public String allowed() { return "bar"; }
769 // Manually inject the Test object, making sure to use the
770 // ContentViewCore#addJavascriptInterface, not the possibly unsafe version.
771 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
772 mTestCallbackHelperContainer.getOnPageFinishedHelper();
773 int currentCallCount = onPageFinishedHelper.getCallCount();
774 runTestOnUiThread(new Runnable() {
777 getContentView().getContentViewCore().addJavascriptInterface(new Test(),
779 getContentView().getContentViewCore().reload(true);
782 onPageFinishedHelper.waitForCallback(currentCallCount);
784 // Test#allowed() should pass, as it is annotated with JavascriptInterface.
785 assertEquals("bar", executeJavaScriptAndGetStringResult(
786 "testObject.allowed()"));
788 // Test#blocked() should fail, as it isn't annotated with JavascriptInterface.
789 assertRaisesException("testObject.blocked()");
790 assertEquals("undefined", executeJavaScriptAndGetStringResult(
791 "typeof testObject.blocked"));
795 @Feature({"AndroidWebView", "Android-JavaBridge"})
796 public void testObjectsInspection() throws Throwable {
799 public String m1() { return "foo"; }
802 public String m2() { return "bar"; }
805 public String m2(int x) { return "bar " + x; }
808 final String jsObjectKeysTestTemplate = "Object.keys(%s).toString()";
809 final String jsForInTestTemplate =
811 " var s=[]; for(var m in %s) s.push(m); return s.join(\",\")" +
813 final String inspectableObjectName = "testObj1";
814 final String nonInspectableObjectName = "testObj2";
816 // Inspection is enabled by default.
817 injectObjectAndReload(new Test(), inspectableObjectName, JavascriptInterface.class);
819 assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
820 String.format(jsObjectKeysTestTemplate, inspectableObjectName)));
821 assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
822 String.format(jsForInTestTemplate, inspectableObjectName)));
824 runTestOnUiThread(new Runnable() {
827 getContentView().getContentViewCore().setAllowJavascriptInterfacesInspection(false);
831 injectObjectAndReload(new Test(), nonInspectableObjectName, JavascriptInterface.class);
833 assertEquals("", executeJavaScriptAndGetStringResult(
834 String.format(jsObjectKeysTestTemplate, nonInspectableObjectName)));
835 assertEquals("", executeJavaScriptAndGetStringResult(
836 String.format(jsForInTestTemplate, nonInspectableObjectName)));