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.
5 package org.chromium.content.browser;
7 import android.test.suitebuilder.annotation.SmallTest;
9 import org.chromium.base.test.util.Feature;
10 import org.chromium.content.browser.JavascriptInterface;
11 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
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;
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
26 * - Calling methods on returned objects
27 * - Multiply injected objects
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;
38 public synchronized void setIntValue(int x) {
40 notifyResultIsReady();
42 public synchronized void setLongValue(long x) {
44 notifyResultIsReady();
46 public synchronized void setStringValue(String x) {
48 notifyResultIsReady();
50 public synchronized void setBooleanValue(boolean x) {
52 notifyResultIsReady();
55 public synchronized int waitForIntValue() {
59 public synchronized long waitForLongValue() {
63 public synchronized String waitForStringValue() {
67 public synchronized boolean waitForBooleanValue() {
73 private static class ObjectWithStaticMethod {
74 public static String staticMethod() {
79 TestController mTestController;
82 protected void setUp() throws Exception {
84 mTestController = new TestController();
85 setUpContentView(mTestController, "testController");
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();
94 protected void injectObjectAndReload(final Object object, final String name) throws Throwable {
95 injectObjectAndReload(object, name, null);
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() {
106 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(object,
107 name, requiredAnnotation);
108 getContentView().reload();
111 onPageFinishedHelper.waitForCallback(currentCallCount);
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 {" +
118 " testController.setBooleanValue(false);" +
119 "} catch (exception) {" +
120 " testController.setBooleanValue(true);" +
122 assertTrue(mTestController.waitForBooleanValue());
126 @Feature({"AndroidWebView", "Android-JavaBridge"})
127 public void testTypeOfInjectedObject() throws Throwable {
128 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
132 @Feature({"AndroidWebView", "Android-JavaBridge"})
133 public void testAdditionNotReflectedUntilReload() throws Throwable {
134 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
135 runTestOnUiThread(new Runnable() {
138 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
139 new Object(), "testObject", null);
142 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
143 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
144 mTestCallbackHelperContainer.getOnPageFinishedHelper();
145 int currentCallCount = onPageFinishedHelper.getCallCount();
146 runTestOnUiThread(new Runnable() {
149 getContentView().reload();
152 onPageFinishedHelper.waitForCallback(currentCallCount);
153 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
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() {
164 getContentView().getContentViewCore().removeJavascriptInterface("testObject");
167 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
168 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
169 mTestCallbackHelperContainer.getOnPageFinishedHelper();
170 int currentCallCount = onPageFinishedHelper.getCallCount();
171 runTestOnUiThread(new Runnable() {
174 getContentView().reload();
177 onPageFinishedHelper.waitForCallback(currentCallCount);
178 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
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() {
190 getContentView().getContentViewCore().removeJavascriptInterface("foo");
191 getContentView().reload();
194 onPageFinishedHelper.waitForCallback(currentCallCount);
195 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo"));
199 @Feature({"AndroidWebView", "Android-JavaBridge"})
200 public void testTypeOfMethod() throws Throwable {
201 assertEquals("function",
202 executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
206 @Feature({"AndroidWebView", "Android-JavaBridge"})
207 public void testTypeOfInvalidMethod() throws Throwable {
208 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
212 @Feature({"AndroidWebView", "Android-JavaBridge"})
213 public void testCallingInvalidMethodRaisesException() throws Throwable {
214 assertRaisesException("testController.foo()");
218 @Feature({"AndroidWebView", "Android-JavaBridge"})
219 public void testUncaughtJavaExceptionRaisesJavaScriptException() throws Throwable {
220 injectObjectAndReload(new Object() {
221 public void method() { throw new RuntimeException("foo"); }
223 assertRaisesException("testObject.method()");
226 // Note that this requires that we can pass a JavaScript string to Java.
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());
235 // Note that this requires that we can pass a JavaScript string to Java.
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());
245 @Feature({"AndroidWebView", "Android-JavaBridge"})
246 public void testPrivateMethodNotExposed() throws Throwable {
247 injectObjectAndReload(new Object() {
248 private void method() {}
249 protected void method2() {}
251 assertEquals("undefined",
252 executeJavaScriptAndGetStringResult("typeof testObject.method"));
253 assertEquals("undefined",
254 executeJavaScriptAndGetStringResult("typeof testObject.method2"));
258 @Feature({"AndroidWebView", "Android-JavaBridge"})
259 public void testReplaceInjectedObject() throws Throwable {
260 injectObjectAndReload(new Object() {
261 public void method() { mTestController.setStringValue("object 1"); }
263 executeJavaScript("testObject.method()");
264 assertEquals("object 1", mTestController.waitForStringValue());
266 injectObjectAndReload(new Object() {
267 public void method() { mTestController.setStringValue("object 2"); }
269 executeJavaScript("testObject.method()");
270 assertEquals("object 2", mTestController.waitForStringValue());
274 @Feature({"AndroidWebView", "Android-JavaBridge"})
275 public void testInjectNullObjectIsIgnored() throws Throwable {
276 injectObjectAndReload(null, "testObject");
277 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
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"));
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"); }
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());
310 @Feature({"AndroidWebView", "Android-JavaBridge"})
311 public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
312 assertRaisesException("testController.setIntValue()");
313 assertRaisesException("testController.setIntValue(42, 42)");
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() {
326 getContentView().reload();
329 onPageFinishedHelper.waitForCallback(currentCallCount);
330 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
334 @Feature({"AndroidWebView", "Android-JavaBridge"})
335 public void testSameObjectInjectedMultipleTimes() throws Throwable {
337 private int mNumMethodInvocations;
338 public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
340 final TestObject testObject = new TestObject();
341 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
342 mTestCallbackHelperContainer.getOnPageFinishedHelper();
343 int currentCallCount = onPageFinishedHelper.getCallCount();
344 runTestOnUiThread(new Runnable() {
347 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
348 testObject, "testObject1", null);
349 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
350 testObject, "testObject2", null);
351 getContentView().reload();
354 onPageFinishedHelper.waitForCallback(currentCallCount);
355 executeJavaScript("testObject1.method()");
356 assertEquals(1, mTestController.waitForIntValue());
357 executeJavaScript("testObject2.method()");
358 assertEquals(2, mTestController.waitForIntValue());
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); }
371 executeJavaScript("testObject.getInnerObject().method(42)");
372 assertEquals(42, mTestController.waitForIntValue());
376 @Feature({"AndroidWebView", "Android-JavaBridge"})
377 public void testReturnedObjectInjectedElsewhere() throws Throwable {
379 private int mNumMethodInvocations;
380 public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
382 final InnerObject innerObject = new InnerObject();
383 final Object object = new Object() {
384 public InnerObject getInnerObject() {
388 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
389 mTestCallbackHelperContainer.getOnPageFinishedHelper();
390 int currentCallCount = onPageFinishedHelper.getCallCount();
391 runTestOnUiThread(new Runnable() {
394 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
395 object, "testObject", null);
396 getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
397 innerObject, "innerObject", null);
398 getContentView().reload();
401 onPageFinishedHelper.waitForCallback(currentCallCount);
402 executeJavaScript("testObject.getInnerObject().method()");
403 assertEquals(1, mTestController.waitForIntValue());
404 executeJavaScript("innerObject.method()");
405 assertEquals(2, mTestController.waitForIntValue());
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());
416 executeJavaScript("testObject.captureThreadId()");
417 final long threadId = mTestController.waitForLongValue();
418 assertFalse(threadId == Thread.currentThread().getId());
419 runTestOnUiThread(new Runnable() {
422 assertFalse(threadId == Thread.currentThread().getId());
428 @Feature({"AndroidWebView", "Android-JavaBridge"})
429 public void testPublicInheritedMethod() throws Throwable {
431 public void method(int x) { mTestController.setIntValue(x); }
433 class Derived extends Base {
435 injectObjectAndReload(new Derived(), "testObject");
436 assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method"));
437 executeJavaScript("testObject.method(42)");
438 assertEquals(42, mTestController.waitForIntValue());
442 @Feature({"AndroidWebView", "Android-JavaBridge"})
443 public void testPrivateInheritedMethod() throws Throwable {
445 private void method() {}
447 class Derived extends Base {
449 injectObjectAndReload(new Derived(), "testObject");
450 assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method"));
454 @Feature({"AndroidWebView", "Android-JavaBridge"})
455 public void testOverriddenMethod() throws Throwable {
457 public void method() { mTestController.setStringValue("base"); }
459 class Derived extends Base {
460 public void method() { mTestController.setStringValue("derived"); }
462 injectObjectAndReload(new Derived(), "testObject");
463 executeJavaScript("testObject.method()");
464 assertEquals("derived", mTestController.waitForStringValue());
468 @Feature({"AndroidWebView", "Android-JavaBridge"})
469 public void testEnumerateMembers() throws Throwable {
470 injectObjectAndReload(new Object() {
471 public void method() {}
472 private void privateMethod() {}
474 private int privateField;
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());
485 @Feature({"AndroidWebView", "Android-JavaBridge"})
486 public void testReflectPublicMethod() throws Throwable {
487 injectObjectAndReload(new Object() {
488 public String method() { return "foo"; }
490 assertEquals("foo", executeJavaScriptAndGetStringResult(
491 "testObject.getClass().getMethod('method', null).invoke(testObject, null)" +
496 @Feature({"AndroidWebView", "Android-JavaBridge"})
497 public void testReflectPublicField() throws Throwable {
498 injectObjectAndReload(new Object() {
499 public String field = "foo";
501 assertEquals("foo", executeJavaScriptAndGetStringResult(
502 "testObject.getClass().getField('field').get(testObject).toString()"));
506 @Feature({"AndroidWebView", "Android-JavaBridge"})
507 public void testReflectPrivateMethodRaisesException() throws Throwable {
508 injectObjectAndReload(new Object() {
509 private void method() {};
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)");
519 @Feature({"AndroidWebView", "Android-JavaBridge"})
520 public void testReflectPrivateFieldRaisesException() throws Throwable {
521 injectObjectAndReload(new Object() {
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)");
532 @Feature({"AndroidWebView", "Android-JavaBridge"})
533 public void testAllowNonAnnotatedMethods() throws Throwable {
534 injectObjectAndReload(new Object() {
535 public String allowed() { return "foo"; }
536 }, "testObject", null);
538 // Test calling a method of an explicitly inherited class (Base#allowed()).
539 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
541 // Test calling a method of an implicitly inherited class (Object#getClass()).
542 assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject.getClass()"));
546 @Feature({"AndroidWebView", "Android-JavaBridge"})
547 public void testAllowOnlyAnnotatedMethods() throws Throwable {
548 injectObjectAndReload(new Object() {
550 public String allowed() { return "foo"; }
552 public String disallowed() { return "bar"; }
553 }, "testObject", JavascriptInterface.class);
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"));
561 // allowed() is marked with the @JavascriptInterface annotation and should be allowed to be
563 assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
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"));
573 @Feature({"AndroidWebView", "Android-JavaBridge"})
574 public void testAnnotationRequirementRetainsPropertyAcrossObjects() throws Throwable {
577 public String safe() { return "foo"; }
579 public String unsafe() { return "bar"; }
584 public Test getTest() { return new Test(); }
587 // First test with safe mode off.
588 injectObjectAndReload(new TestReturner(), "unsafeTestObject", null);
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()"));
597 // Now test with safe mode on.
598 injectObjectAndReload(new TestReturner(), "safeTestObject", JavascriptInterface.class);
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"));
615 @Feature({"AndroidWebView", "Android-JavaBridge"})
616 public void testAnnotationDoesNotGetInherited() throws Throwable {
619 public void base() { }
622 class Child extends Base {
624 public void base() { }
627 injectObjectAndReload(new Child(), "testObject", JavascriptInterface.class);
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"));
636 @SuppressWarnings("javadoc")
637 @Retention(RetentionPolicy.RUNTIME)
638 @Target({ElementType.METHOD})
639 @interface TestAnnotation {
643 @Feature({"AndroidWebView", "Android-JavaBridge"})
644 public void testCustomAnnotationRestriction() throws Throwable {
647 public String checkTestAnnotationFoo() { return "bar"; }
650 public String checkJavascriptInterfaceFoo() { return "bar"; }
653 // Inject javascriptInterfaceObj and require the JavascriptInterface annotation.
654 injectObjectAndReload(new Test(), "javascriptInterfaceObj", JavascriptInterface.class);
656 // Test#testAnnotationFoo() should fail, as it isn't annotated with JavascriptInterface.
657 assertRaisesException("javascriptInterfaceObj.checkTestAnnotationFoo()");
658 assertEquals("undefined", executeJavaScriptAndGetStringResult(
659 "typeof javascriptInterfaceObj.checkTestAnnotationFoo"));
661 // Test#javascriptInterfaceFoo() should pass, as it is annotated with JavascriptInterface.
662 assertEquals("bar", executeJavaScriptAndGetStringResult(
663 "javascriptInterfaceObj.checkJavascriptInterfaceFoo()"));
665 // Inject testAnnotationObj and require the TestAnnotation annotation.
666 injectObjectAndReload(new Test(), "testAnnotationObj", TestAnnotation.class);
668 // Test#testAnnotationFoo() should pass, as it is annotated with TestAnnotation.
669 assertEquals("bar", executeJavaScriptAndGetStringResult(
670 "testAnnotationObj.checkTestAnnotationFoo()"));
672 // Test#javascriptInterfaceFoo() should fail, as it isn't annotated with TestAnnotation.
673 assertRaisesException("testAnnotationObj.checkJavascriptInterfaceFoo()");
674 assertEquals("undefined", executeJavaScriptAndGetStringResult(
675 "typeof testAnnotationObj.checkJavascriptInterfaceFoo"));
679 @Feature({"AndroidWebView", "Android-JavaBridge"})
680 public void testAddJavascriptInterfaceIsSafeByDefault() throws Throwable {
682 public String blocked() { return "bar"; }
685 public String allowed() { return "bar"; }
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() {
696 getContentView().getContentViewCore().addJavascriptInterface(new Test(),
698 getContentView().reload();
701 onPageFinishedHelper.waitForCallback(currentCallCount);
703 // Test#allowed() should pass, as it is annotated with JavascriptInterface.
704 assertEquals("bar", executeJavaScriptAndGetStringResult(
705 "testObject.allowed()"));
707 // Test#blocked() should fail, as it isn't annotated with JavascriptInterface.
708 assertRaisesException("testObject.blocked()");
709 assertEquals("undefined", executeJavaScriptAndGetStringResult(
710 "typeof testObject.blocked"));