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