- add sources.
[platform/framework/web/crosswalk.git] / src / content / public / android / javatests / src / org / chromium / content / browser / JavaBridgeBasicsTest.java
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
5 package org.chromium.content.browser;
6
7 import android.test.suitebuilder.annotation.SmallTest;
8
9 import org.chromium.base.test.util.Feature;
10 import org.chromium.content.browser.JavascriptInterface;
11 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
12
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
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  */
31 public 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 }