JavaBridgeBasicsTest.java revision 116680a4aac90f2aa7413d9095a592090648e557
12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright 2012 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)package org.chromium.content.browser;
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.test.suitebuilder.annotation.SmallTest;
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import org.chromium.base.test.util.Feature;
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import org.chromium.content_shell_apk.ContentShellActivity;
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import java.lang.annotation.Annotation;
1403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)import java.lang.annotation.ElementType;
15a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)import java.lang.annotation.Retention;
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.lang.annotation.RetentionPolicy;
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.lang.annotation.Target;
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.lang.ref.WeakReference;
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/**
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * Part of the test suite for the Java Bridge. Tests a number of features including ...
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - The type of injected objects
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - The type of their methods
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - Replacing objects
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - Removing objects
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - Access control
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - Calling methods on returned objects
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - Multiply injected objects
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - Threading
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * - Inheritance
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)public class JavaBridgeBasicsTest extends JavaBridgeTestBase {
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private class TestController extends Controller {
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        private int mIntValue;
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        private long mLongValue;
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        private String mStringValue;
37f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        private boolean mBooleanValue;
38f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
39f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        public synchronized void setIntValue(int x) {
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mIntValue = x;
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            notifyResultIsReady();
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        public synchronized void setLongValue(long x) {
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mLongValue = x;
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            notifyResultIsReady();
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
47c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        public synchronized void setStringValue(String x) {
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mStringValue = x;
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            notifyResultIsReady();
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        public synchronized void setBooleanValue(boolean x) {
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mBooleanValue = x;
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            notifyResultIsReady();
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        public synchronized int waitForIntValue() {
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            waitForResult();
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return mIntValue;
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        public synchronized long waitForLongValue() {
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            waitForResult();
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            return mLongValue;
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        public synchronized String waitForStringValue() {
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            waitForResult();
66c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            return mStringValue;
67a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        }
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        public synchronized boolean waitForBooleanValue() {
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            waitForResult();
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            return mBooleanValue;
71a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        }
72a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
73a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        public synchronized String getStringValue() {
74a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch            return mStringValue;
75a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        }
76a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    }
77a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
78a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    private static class ObjectWithStaticMethod {
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        public static String staticMethod() {
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            return "foo";
81a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        }
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    TestController mTestController;
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @Override
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    protected void setUp() throws Exception {
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        super.setUp();
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        mTestController = new TestController();
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        setUpContentView(mTestController, "testController");
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @Override
94c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    protected ContentShellActivity launchContentShellWithUrl(String url) {
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        // Expose a global function "gc()" into pages.
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return launchContentShellWithUrlAndCommandLineArgs(
97f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                url, new String[]{ "--js-flags=--expose-gc" });
98f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Note that this requires that we can pass a JavaScript string to Java.
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    protected String executeJavaScriptAndGetStringResult(String script) throws Throwable {
1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        executeJavaScript("testController.setStringValue(" + script + ");");
10358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        return mTestController.waitForStringValue();
10458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    }
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    protected void injectObjectAndReload(final Object object, final String name) throws Throwable {
1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        injectObjectAndReload(object, name, null);
1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    protected void injectObjectAndReload(final Object object, final String name,
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            final Class<? extends Annotation> requiredAnnotation) throws Throwable {
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                mTestCallbackHelperContainer.getOnPageFinishedHelper();
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        int currentCallCount = onPageFinishedHelper.getCallCount();
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        runTestOnUiThread(new Runnable() {
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            @Override
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            public void run() {
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                getContentViewCore().addPossiblyUnsafeJavascriptInterface(object,
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                        name, requiredAnnotation);
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                getContentViewCore().reload(true);
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            }
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        });
123a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        onPageFinishedHelper.waitForCallback(currentCallCount);
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    protected void synchronousPageReload() throws Throwable {
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                mTestCallbackHelperContainer.getOnPageFinishedHelper();
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        int currentCallCount = onPageFinishedHelper.getCallCount();
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        runTestOnUiThread(new Runnable() {
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            @Override
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            public void run() {
1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                getContentViewCore().reload(true);
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            }
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        });
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        onPageFinishedHelper.waitForCallback(currentCallCount);
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Note that this requires that we can pass a JavaScript boolean to Java.
140a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private void assertRaisesException(String script) throws Throwable {
141f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        executeJavaScript("try {" +
142f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                          script + ";" +
143f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                          "  testController.setBooleanValue(false);" +
144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                          "} catch (exception) {" +
145effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                          "  testController.setBooleanValue(true);" +
146effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                          "}");
147f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        assertTrue(mTestController.waitForBooleanValue());
148f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
149f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    @SmallTest
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @Feature({"AndroidWebView", "Android-JavaBridge"})
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public void testTypeOfInjectedObject() throws Throwable {
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @SmallTest
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    @Feature({"AndroidWebView", "Android-JavaBridge"})
158c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    public void testAdditionNotReflectedUntilReload() throws Throwable {
159c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
160c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        runTestOnUiThread(new Runnable() {
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            @Override
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            public void run() {
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                getContentViewCore().addPossiblyUnsafeJavascriptInterface(
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                        new Object(), "testObject", null);
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            }
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        });
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        synchronousPageReload();
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @SmallTest
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @Feature({"AndroidWebView", "Android-JavaBridge"})
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public void testRemovalNotReflectedUntilReload() throws Throwable {
1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        injectObjectAndReload(new Object(), "testObject");
1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        runTestOnUiThread(new Runnable() {
1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            @Override
1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            public void run() {
1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                getContentViewCore().removeJavascriptInterface("testObject");
1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            }
1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        });
1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        synchronousPageReload();
1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @SmallTest
1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @Feature({"AndroidWebView", "Android-JavaBridge"})
190    public void testRemoveObjectNotAdded() throws Throwable {
191        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
192                mTestCallbackHelperContainer.getOnPageFinishedHelper();
193        int currentCallCount = onPageFinishedHelper.getCallCount();
194        runTestOnUiThread(new Runnable() {
195            @Override
196            public void run() {
197                getContentViewCore().removeJavascriptInterface("foo");
198                getContentViewCore().reload(true);
199            }
200        });
201        onPageFinishedHelper.waitForCallback(currentCallCount);
202        assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo"));
203    }
204
205    @SmallTest
206    @Feature({"AndroidWebView", "Android-JavaBridge"})
207    public void testTypeOfMethod() throws Throwable {
208        assertEquals("function",
209                executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
210    }
211
212    @SmallTest
213    @Feature({"AndroidWebView", "Android-JavaBridge"})
214    public void testTypeOfInvalidMethod() throws Throwable {
215        assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
216    }
217
218    @SmallTest
219    @Feature({"AndroidWebView", "Android-JavaBridge"})
220    public void testCallingInvalidMethodRaisesException() throws Throwable {
221        assertRaisesException("testController.foo()");
222    }
223
224    @SmallTest
225    @Feature({"AndroidWebView", "Android-JavaBridge"})
226    public void testUncaughtJavaExceptionRaisesJavaScriptException() throws Throwable {
227        injectObjectAndReload(new Object() {
228            public void method() { throw new RuntimeException("foo"); }
229        }, "testObject");
230        assertRaisesException("testObject.method()");
231    }
232
233    // Note that this requires that we can pass a JavaScript string to Java.
234    @SmallTest
235    @Feature({"AndroidWebView", "Android-JavaBridge"})
236    public void testTypeOfStaticMethod() throws Throwable {
237        injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
238        executeJavaScript("testController.setStringValue(typeof testObject.staticMethod)");
239        assertEquals("function", mTestController.waitForStringValue());
240    }
241
242    // Note that this requires that we can pass a JavaScript string to Java.
243    @SmallTest
244    @Feature({"AndroidWebView", "Android-JavaBridge"})
245    public void testCallStaticMethod() throws Throwable {
246        injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
247        executeJavaScript("testController.setStringValue(testObject.staticMethod())");
248        assertEquals("foo", mTestController.waitForStringValue());
249    }
250
251    @SmallTest
252    @Feature({"AndroidWebView", "Android-JavaBridge"})
253    public void testPrivateMethodNotExposed() throws Throwable {
254        injectObjectAndReload(new Object() {
255            private void method() {}
256            protected void method2() {}
257        }, "testObject");
258        assertEquals("undefined",
259                executeJavaScriptAndGetStringResult("typeof testObject.method"));
260        assertEquals("undefined",
261                executeJavaScriptAndGetStringResult("typeof testObject.method2"));
262    }
263
264    @SmallTest
265    @Feature({"AndroidWebView", "Android-JavaBridge"})
266    public void testReplaceInjectedObject() throws Throwable {
267        injectObjectAndReload(new Object() {
268            public void method() { mTestController.setStringValue("object 1"); }
269        }, "testObject");
270        executeJavaScript("testObject.method()");
271        assertEquals("object 1", mTestController.waitForStringValue());
272
273        injectObjectAndReload(new Object() {
274            public void method() { mTestController.setStringValue("object 2"); }
275        }, "testObject");
276        executeJavaScript("testObject.method()");
277        assertEquals("object 2", mTestController.waitForStringValue());
278    }
279
280    @SmallTest
281    @Feature({"AndroidWebView", "Android-JavaBridge"})
282    public void testInjectNullObjectIsIgnored() throws Throwable {
283        injectObjectAndReload(null, "testObject");
284        assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
285    }
286
287    @SmallTest
288    @Feature({"AndroidWebView", "Android-JavaBridge"})
289    public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable {
290        injectObjectAndReload(new Object(), "testObject");
291        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
292        injectObjectAndReload(null, "testObject");
293        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
294    }
295
296    @SmallTest
297    @Feature({"AndroidWebView", "Android-JavaBridge"})
298    public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable {
299        injectObjectAndReload(new Object() {
300            public void method() { mTestController.setStringValue("0 args"); }
301            public void method(int x) { mTestController.setStringValue("1 arg"); }
302            public void method(int x, int y) { mTestController.setStringValue("2 args"); }
303        }, "testObject");
304        executeJavaScript("testObject.method()");
305        assertEquals("0 args", mTestController.waitForStringValue());
306        executeJavaScript("testObject.method(42)");
307        assertEquals("1 arg", mTestController.waitForStringValue());
308        executeJavaScript("testObject.method(null)");
309        assertEquals("1 arg", mTestController.waitForStringValue());
310        executeJavaScript("testObject.method(undefined)");
311        assertEquals("1 arg", mTestController.waitForStringValue());
312        executeJavaScript("testObject.method(42, 42)");
313        assertEquals("2 args", mTestController.waitForStringValue());
314    }
315
316    @SmallTest
317    @Feature({"AndroidWebView", "Android-JavaBridge"})
318    public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
319        assertRaisesException("testController.setIntValue()");
320        assertRaisesException("testController.setIntValue(42, 42)");
321    }
322
323    @SmallTest
324    @Feature({"AndroidWebView", "Android-JavaBridge"})
325    public void testObjectPersistsAcrossPageLoads() throws Throwable {
326        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
327        synchronousPageReload();
328        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
329    }
330
331    @SmallTest
332    @Feature({"AndroidWebView", "Android-JavaBridge"})
333    public void testCustomPropertiesCleanedUpOnPageReloads() throws Throwable {
334        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
335        executeJavaScript("testController.myProperty = 42;");
336        assertEquals("42", executeJavaScriptAndGetStringResult("testController.myProperty"));
337        synchronousPageReload();
338        assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
339        assertEquals("undefined", executeJavaScriptAndGetStringResult("testController.myProperty"));
340    }
341
342    @SmallTest
343    @Feature({"AndroidWebView", "Android-JavaBridge"})
344    public void testSameObjectInjectedMultipleTimes() throws Throwable {
345        class TestObject {
346            private int mNumMethodInvocations;
347            public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
348        }
349        final TestObject testObject = new TestObject();
350        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
351                mTestCallbackHelperContainer.getOnPageFinishedHelper();
352        int currentCallCount = onPageFinishedHelper.getCallCount();
353        runTestOnUiThread(new Runnable() {
354            @Override
355            public void run() {
356                getContentViewCore().addPossiblyUnsafeJavascriptInterface(
357                        testObject, "testObject1", null);
358                getContentViewCore().addPossiblyUnsafeJavascriptInterface(
359                        testObject, "testObject2", null);
360                getContentViewCore().reload(true);
361            }
362        });
363        onPageFinishedHelper.waitForCallback(currentCallCount);
364        executeJavaScript("testObject1.method()");
365        assertEquals(1, mTestController.waitForIntValue());
366        executeJavaScript("testObject2.method()");
367        assertEquals(2, mTestController.waitForIntValue());
368    }
369
370    @SmallTest
371    @Feature({"AndroidWebView", "Android-JavaBridge"})
372    public void testCallMethodOnReturnedObject() throws Throwable {
373        injectObjectAndReload(new Object() {
374            public Object getInnerObject() {
375                return new Object() {
376                    public void method(int x) { mTestController.setIntValue(x); }
377                };
378            }
379        }, "testObject");
380        executeJavaScript("testObject.getInnerObject().method(42)");
381        assertEquals(42, mTestController.waitForIntValue());
382    }
383
384    @SmallTest
385    @Feature({"AndroidWebView", "Android-JavaBridge"})
386    public void testReturnedObjectInjectedElsewhere() throws Throwable {
387        class InnerObject {
388            private int mNumMethodInvocations;
389            public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
390        }
391        final InnerObject innerObject = new InnerObject();
392        final Object object = new Object() {
393            public InnerObject getInnerObject() {
394                return innerObject;
395            }
396        };
397        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
398                mTestCallbackHelperContainer.getOnPageFinishedHelper();
399        int currentCallCount = onPageFinishedHelper.getCallCount();
400        runTestOnUiThread(new Runnable() {
401            @Override
402            public void run() {
403                getContentViewCore().addPossiblyUnsafeJavascriptInterface(
404                        object, "testObject", null);
405                getContentViewCore().addPossiblyUnsafeJavascriptInterface(
406                        innerObject, "innerObject", null);
407                getContentViewCore().reload(true);
408            }
409        });
410        onPageFinishedHelper.waitForCallback(currentCallCount);
411        executeJavaScript("testObject.getInnerObject().method()");
412        assertEquals(1, mTestController.waitForIntValue());
413        executeJavaScript("innerObject.method()");
414        assertEquals(2, mTestController.waitForIntValue());
415    }
416
417    // Verify that Java objects returned from bridge object methods are dereferenced
418    // on the Java side once they have been fully dereferenced on the JS side.
419    // Failing this test would mean that methods returning objects effectively create a memory
420    // leak.
421    @SmallTest
422    @Feature({"AndroidWebView", "Android-JavaBridge"})
423    public void testReturnedObjectIsGarbageCollected() throws Throwable {
424        // Make sure V8 exposes "gc" property on the global object (enabled with --expose-gc flag)
425        assertEquals("function", executeJavaScriptAndGetStringResult("typeof gc"));
426        class InnerObject {
427        }
428        class TestObject {
429            public InnerObject getInnerObject() {
430                InnerObject inner = new InnerObject();
431                weakRefForInner = new WeakReference<InnerObject>(inner);
432                return inner;
433            }
434            // A weak reference is used to check InnerObject instance reachability.
435            WeakReference<InnerObject> weakRefForInner;
436        }
437        TestObject object = new TestObject();
438        injectObjectAndReload(object, "testObject");
439        // Initially, store a reference to the inner object in JS to make sure it's not
440        // garbage-collected prematurely.
441        assertEquals("object", executeJavaScriptAndGetStringResult(
442                        "(function() { " +
443                        "globalInner = testObject.getInnerObject(); return typeof globalInner; " +
444                        "})()"));
445        assertTrue(object.weakRefForInner.get() != null);
446        // Check that returned Java object is being held by the Java bridge, thus it's not
447        // collected.  Note that despite that what JavaDoc says about invoking "gc()", both Dalvik
448        // and ART actually run the collector.
449        Runtime.getRuntime().gc();
450        assertTrue(object.weakRefForInner.get() != null);
451        // Now dereference the inner object in JS and run GC to collect the interface object.
452        assertEquals("true", executeJavaScriptAndGetStringResult(
453                        "(function() { " +
454                        "delete globalInner; gc(); return (typeof globalInner == 'undefined'); " +
455                        "})()"));
456        // Force GC on the Java side again. The bridge had to release the inner object, so it must
457        // be collected this time.
458        Runtime.getRuntime().gc();
459        assertEquals(null, object.weakRefForInner.get());
460    }
461
462    @SmallTest
463    @Feature({"AndroidWebView", "Android-JavaBridge"})
464    public void testSameReturnedObjectUsesSameWrapper() throws Throwable {
465        class InnerObject {
466        }
467        final InnerObject innerObject = new InnerObject();
468        final Object injectedTestObject = new Object() {
469            public InnerObject getInnerObject() {
470                return innerObject;
471            }
472        };
473        injectObjectAndReload(injectedTestObject, "injectedTestObject");
474        executeJavaScript("inner1 = injectedTestObject.getInnerObject()");
475        executeJavaScript("inner2 = injectedTestObject.getInnerObject()");
476        assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner1"));
477        assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner2"));
478        assertEquals("true", executeJavaScriptAndGetStringResult("inner1 === inner2"));
479    }
480
481    @SmallTest
482    @Feature({"AndroidWebView", "Android-JavaBridge"})
483    public void testMethodInvokedOnBackgroundThread() throws Throwable {
484        injectObjectAndReload(new Object() {
485            public void captureThreadId() {
486                mTestController.setLongValue(Thread.currentThread().getId());
487            }
488        }, "testObject");
489        executeJavaScript("testObject.captureThreadId()");
490        final long threadId = mTestController.waitForLongValue();
491        assertFalse(threadId == Thread.currentThread().getId());
492        runTestOnUiThread(new Runnable() {
493            @Override
494            public void run() {
495                assertFalse(threadId == Thread.currentThread().getId());
496            }
497        });
498    }
499
500    @SmallTest
501    @Feature({"AndroidWebView", "Android-JavaBridge"})
502    public void testPublicInheritedMethod() throws Throwable {
503        class Base {
504            public void method(int x) { mTestController.setIntValue(x); }
505        }
506        class Derived extends Base {
507        }
508        injectObjectAndReload(new Derived(), "testObject");
509        assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method"));
510        executeJavaScript("testObject.method(42)");
511        assertEquals(42, mTestController.waitForIntValue());
512    }
513
514    @SmallTest
515    @Feature({"AndroidWebView", "Android-JavaBridge"})
516    public void testPrivateInheritedMethod() throws Throwable {
517        class Base {
518            private void method() {}
519        }
520        class Derived extends Base {
521        }
522        injectObjectAndReload(new Derived(), "testObject");
523        assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method"));
524    }
525
526    @SmallTest
527    @Feature({"AndroidWebView", "Android-JavaBridge"})
528    public void testOverriddenMethod() throws Throwable {
529        class Base {
530            public void method() { mTestController.setStringValue("base"); }
531        }
532        class Derived extends Base {
533            @Override
534            public void method() { mTestController.setStringValue("derived"); }
535        }
536        injectObjectAndReload(new Derived(), "testObject");
537        executeJavaScript("testObject.method()");
538        assertEquals("derived", mTestController.waitForStringValue());
539    }
540
541    @SmallTest
542    @Feature({"AndroidWebView", "Android-JavaBridge"})
543    public void testEnumerateMembers() throws Throwable {
544        injectObjectAndReload(new Object() {
545            public void method() {}
546            private void privateMethod() {}
547            public int field;
548            private int privateField;
549        }, "testObject");
550        executeJavaScript(
551                "var result = \"\"; " +
552                "for (x in testObject) { result += \" \" + x } " +
553                "testController.setStringValue(result);");
554        assertEquals(" equals getClass hashCode method notify notifyAll toString wait",
555                mTestController.waitForStringValue());
556    }
557
558    @SmallTest
559    @Feature({"AndroidWebView", "Android-JavaBridge"})
560    public void testReflectPublicMethod() throws Throwable {
561        injectObjectAndReload(new Object() {
562            public Class<?> myGetClass() { return getClass(); }
563            public String method() { return "foo"; }
564        }, "testObject");
565        assertEquals("foo", executeJavaScriptAndGetStringResult(
566                "testObject.myGetClass().getMethod('method', null).invoke(testObject, null)" +
567                ".toString()"));
568    }
569
570    @SmallTest
571    @Feature({"AndroidWebView", "Android-JavaBridge"})
572    public void testReflectPublicField() throws Throwable {
573        injectObjectAndReload(new Object() {
574            public Class<?> myGetClass() { return getClass(); }
575            public String field = "foo";
576        }, "testObject");
577        assertEquals("foo", executeJavaScriptAndGetStringResult(
578                "testObject.myGetClass().getField('field').get(testObject).toString()"));
579    }
580
581    @SmallTest
582    @Feature({"AndroidWebView", "Android-JavaBridge"})
583    public void testReflectPrivateMethodRaisesException() throws Throwable {
584        injectObjectAndReload(new Object() {
585            public Class<?> myGetClass() { return getClass(); }
586            private void method() {};
587        }, "testObject");
588        assertRaisesException("testObject.myGetClass().getMethod('method', null)");
589        // getDeclaredMethod() is able to access a private method, but invoke()
590        // throws a Java exception.
591        assertRaisesException(
592                "testObject.myGetClass().getDeclaredMethod('method', null)." +
593                "invoke(testObject, null)");
594    }
595
596    @SmallTest
597    @Feature({"AndroidWebView", "Android-JavaBridge"})
598    public void testReflectPrivateFieldRaisesException() throws Throwable {
599        injectObjectAndReload(new Object() {
600            public Class<?> myGetClass() { return getClass(); }
601            private int field;
602        }, "testObject");
603        assertRaisesException("testObject.myGetClass().getField('field')");
604        // getDeclaredField() is able to access a private field, but getInt()
605        // throws a Java exception.
606        assertRaisesException(
607                "testObject.myGetClass().getDeclaredField('field').getInt(testObject)");
608    }
609
610    @SmallTest
611    @Feature({"AndroidWebView", "Android-JavaBridge"})
612    public void testAllowNonAnnotatedMethods() throws Throwable {
613        injectObjectAndReload(new Object() {
614            public String allowed() { return "foo"; }
615        }, "testObject", null);
616
617        // Test calling a method of an explicitly inherited class (Base#allowed()).
618        assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
619
620        // Test calling a method of an implicitly inherited class (Object#toString()).
621        assertEquals("string", executeJavaScriptAndGetStringResult("typeof testObject.toString()"));
622    }
623
624    @SmallTest
625    @Feature({"AndroidWebView", "Android-JavaBridge"})
626    public void testAllowOnlyAnnotatedMethods() throws Throwable {
627        injectObjectAndReload(new Object() {
628            @JavascriptInterface
629            public String allowed() { return "foo"; }
630
631            public String disallowed() { return "bar"; }
632        }, "testObject", JavascriptInterface.class);
633
634        // getClass() is an Object method and does not have the @JavascriptInterface annotation and
635        // should not be able to be called.
636        assertRaisesException("testObject.getClass()");
637        assertEquals("undefined", executeJavaScriptAndGetStringResult(
638                "typeof testObject.getClass"));
639
640        // allowed() is marked with the @JavascriptInterface annotation and should be allowed to be
641        // called.
642        assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
643
644        // disallowed() is not marked with the @JavascriptInterface annotation and should not be
645        // able to be called.
646        assertRaisesException("testObject.disallowed()");
647        assertEquals("undefined", executeJavaScriptAndGetStringResult(
648                "typeof testObject.disallowed"));
649    }
650
651    @SmallTest
652    @Feature({"AndroidWebView", "Android-JavaBridge"})
653    public void testAnnotationRequirementRetainsPropertyAcrossObjects() throws Throwable {
654        class Test {
655            @JavascriptInterface
656            public String safe() { return "foo"; }
657
658            public String unsafe() { return "bar"; }
659        }
660
661        class TestReturner {
662            @JavascriptInterface
663            public Test getTest() { return new Test(); }
664        }
665
666        // First test with safe mode off.
667        injectObjectAndReload(new TestReturner(), "unsafeTestObject", null);
668
669        // safe() should be able to be called regardless of whether or not we are in safe mode.
670        assertEquals("foo", executeJavaScriptAndGetStringResult(
671                "unsafeTestObject.getTest().safe()"));
672        // unsafe() should be able to be called because we are not in safe mode.
673        assertEquals("bar", executeJavaScriptAndGetStringResult(
674                "unsafeTestObject.getTest().unsafe()"));
675
676        // Now test with safe mode on.
677        injectObjectAndReload(new TestReturner(), "safeTestObject", JavascriptInterface.class);
678
679        // safe() should be able to be called regardless of whether or not we are in safe mode.
680        assertEquals("foo", executeJavaScriptAndGetStringResult(
681                "safeTestObject.getTest().safe()"));
682        // unsafe() should not be able to be called because we are in safe mode.
683        assertRaisesException("safeTestObject.getTest().unsafe()");
684        assertEquals("undefined", executeJavaScriptAndGetStringResult(
685                "typeof safeTestObject.getTest().unsafe"));
686        // getClass() is an Object method and does not have the @JavascriptInterface annotation and
687        // should not be able to be called.
688        assertRaisesException("safeTestObject.getTest().getClass()");
689        assertEquals("undefined", executeJavaScriptAndGetStringResult(
690                "typeof safeTestObject.getTest().getClass"));
691    }
692
693    @SmallTest
694    @Feature({"AndroidWebView", "Android-JavaBridge"})
695    public void testAnnotationDoesNotGetInherited() throws Throwable {
696        class Base {
697            @JavascriptInterface
698            public void base() { }
699        }
700
701        class Child extends Base {
702            @Override
703            public void base() { }
704        }
705
706        injectObjectAndReload(new Child(), "testObject", JavascriptInterface.class);
707
708        // base() is inherited.  The inherited method does not have the @JavascriptInterface
709        // annotation and should not be able to be called.
710        assertRaisesException("testObject.base()");
711        assertEquals("undefined", executeJavaScriptAndGetStringResult(
712                "typeof testObject.base"));
713    }
714
715    @SuppressWarnings("javadoc")
716    @Retention(RetentionPolicy.RUNTIME)
717    @Target({ElementType.METHOD})
718    @interface TestAnnotation {
719    }
720
721    @SmallTest
722    @Feature({"AndroidWebView", "Android-JavaBridge"})
723    public void testCustomAnnotationRestriction() throws Throwable {
724        class Test {
725            @TestAnnotation
726            public String checkTestAnnotationFoo() { return "bar"; }
727
728            @JavascriptInterface
729            public String checkJavascriptInterfaceFoo() { return "bar"; }
730        }
731
732        // Inject javascriptInterfaceObj and require the JavascriptInterface annotation.
733        injectObjectAndReload(new Test(), "javascriptInterfaceObj", JavascriptInterface.class);
734
735        // Test#testAnnotationFoo() should fail, as it isn't annotated with JavascriptInterface.
736        assertRaisesException("javascriptInterfaceObj.checkTestAnnotationFoo()");
737        assertEquals("undefined", executeJavaScriptAndGetStringResult(
738                "typeof javascriptInterfaceObj.checkTestAnnotationFoo"));
739
740        // Test#javascriptInterfaceFoo() should pass, as it is annotated with JavascriptInterface.
741        assertEquals("bar", executeJavaScriptAndGetStringResult(
742                "javascriptInterfaceObj.checkJavascriptInterfaceFoo()"));
743
744        // Inject testAnnotationObj and require the TestAnnotation annotation.
745        injectObjectAndReload(new Test(), "testAnnotationObj", TestAnnotation.class);
746
747        // Test#testAnnotationFoo() should pass, as it is annotated with TestAnnotation.
748        assertEquals("bar", executeJavaScriptAndGetStringResult(
749                "testAnnotationObj.checkTestAnnotationFoo()"));
750
751        // Test#javascriptInterfaceFoo() should fail, as it isn't annotated with TestAnnotation.
752        assertRaisesException("testAnnotationObj.checkJavascriptInterfaceFoo()");
753        assertEquals("undefined", executeJavaScriptAndGetStringResult(
754                "typeof testAnnotationObj.checkJavascriptInterfaceFoo"));
755    }
756
757    @SmallTest
758    @Feature({"AndroidWebView", "Android-JavaBridge"})
759    public void testAddJavascriptInterfaceIsSafeByDefault() throws Throwable {
760        class Test {
761            public String blocked() { return "bar"; }
762
763            @JavascriptInterface
764            public String allowed() { return "bar"; }
765        }
766
767        // Manually inject the Test object, making sure to use the
768        // ContentViewCore#addJavascriptInterface, not the possibly unsafe version.
769        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
770                mTestCallbackHelperContainer.getOnPageFinishedHelper();
771        int currentCallCount = onPageFinishedHelper.getCallCount();
772        runTestOnUiThread(new Runnable() {
773            @Override
774            public void run() {
775                getContentViewCore().addJavascriptInterface(new Test(),
776                        "testObject");
777                getContentViewCore().reload(true);
778            }
779        });
780        onPageFinishedHelper.waitForCallback(currentCallCount);
781
782        // Test#allowed() should pass, as it is annotated with JavascriptInterface.
783        assertEquals("bar", executeJavaScriptAndGetStringResult(
784                "testObject.allowed()"));
785
786        // Test#blocked() should fail, as it isn't annotated with JavascriptInterface.
787        assertRaisesException("testObject.blocked()");
788        assertEquals("undefined", executeJavaScriptAndGetStringResult(
789                "typeof testObject.blocked"));
790    }
791
792    @SmallTest
793    @Feature({"AndroidWebView", "Android-JavaBridge"})
794    public void testObjectsInspection() throws Throwable {
795        class Test {
796            @JavascriptInterface
797            public String m1() { return "foo"; }
798
799            @JavascriptInterface
800            public String m2() { return "bar"; }
801
802            @JavascriptInterface
803            public String m2(int x) { return "bar " + x; }
804        }
805
806        final String jsObjectKeysTestTemplate = "Object.keys(%s).toString()";
807        final String jsForInTestTemplate =
808                "(function(){" +
809                "  var s=[]; for(var m in %s) s.push(m); return s.join(\",\")" +
810                "})()";
811        final String inspectableObjectName = "testObj1";
812        final String nonInspectableObjectName = "testObj2";
813
814        // Inspection is enabled by default.
815        injectObjectAndReload(new Test(), inspectableObjectName, JavascriptInterface.class);
816
817        assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
818                        String.format(jsObjectKeysTestTemplate, inspectableObjectName)));
819        assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
820                        String.format(jsForInTestTemplate, inspectableObjectName)));
821
822        runTestOnUiThread(new Runnable() {
823            @Override
824            public void run() {
825                getContentViewCore().setAllowJavascriptInterfacesInspection(false);
826            }
827        });
828
829        injectObjectAndReload(new Test(), nonInspectableObjectName, JavascriptInterface.class);
830
831        assertEquals("", executeJavaScriptAndGetStringResult(
832                        String.format(jsObjectKeysTestTemplate, nonInspectableObjectName)));
833        assertEquals("", executeJavaScriptAndGetStringResult(
834                        String.format(jsForInTestTemplate, nonInspectableObjectName)));
835    }
836
837    @SmallTest
838    @Feature({"AndroidWebView", "Android-JavaBridge"})
839    public void testAccessToObjectGetClassIsBlocked() throws Throwable {
840        injectObjectAndReload(new Object(), "testObject");
841        assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.getClass"));
842        assertRaisesException("testObject.getClass()");
843    }
844}
845