This source file includes following definitions.
- setIntValue
- setLongValue
- setStringValue
- setBooleanValue
- waitForIntValue
- waitForLongValue
- waitForStringValue
- waitForBooleanValue
- getStringValue
- staticMethod
- setUp
- launchContentShellWithUrl
- executeJavaScriptAndGetStringResult
- injectObjectAndReload
- injectObjectAndReload
- synchronousPageReload
- assertRaisesException
- Feature
- testTypeOfInjectedObject
- Feature
- testAdditionNotReflectedUntilReload
- Feature
- testRemovalNotReflectedUntilReload
- Feature
- testRemoveObjectNotAdded
- Feature
- testTypeOfMethod
- Feature
- testTypeOfInvalidMethod
- Feature
- testCallingInvalidMethodRaisesException
- Feature
- testUncaughtJavaExceptionRaisesJavaScriptException
- Feature
- testTypeOfStaticMethod
- Feature
- testCallStaticMethod
- Feature
- testPrivateMethodNotExposed
- Feature
- testReplaceInjectedObject
- Feature
- testInjectNullObjectIsIgnored
- Feature
- testReplaceInjectedObjectWithNullObjectIsIgnored
- Feature
- testCallOverloadedMethodWithDifferentNumberOfArguments
- Feature
- testCallMethodWithWrongNumberOfArgumentsRaisesException
- Feature
- testObjectPersistsAcrossPageLoads
- Feature
- testClientPropertiesPersistAcrossPageLoads
- Feature
- testSameObjectInjectedMultipleTimes
- method
- Feature
- testCallMethodOnReturnedObject
- Feature
- testReturnedObjectInjectedElsewhere
- method
- Feature
- testReturnedObjectIsGarbageCollected
- getInnerObject
- testSameReturnedObjectUsesSameWrapper
- Feature
- testMethodInvokedOnBackgroundThread
- Feature
- testPublicInheritedMethod
- method
- Feature
- testPrivateInheritedMethod
- method
- Feature
- testOverriddenMethod
- method
- method
- Feature
- testEnumerateMembers
- Feature
- testReflectPublicMethod
- Feature
- testReflectPublicField
- Feature
- testReflectPrivateMethodRaisesException
- Feature
- testReflectPrivateFieldRaisesException
- Feature
- testAllowNonAnnotatedMethods
- Feature
- testAllowOnlyAnnotatedMethods
- Feature
- testAnnotationRequirementRetainsPropertyAcrossObjects
- safe
- unsafe
- getTest
- Feature
- testAnnotationDoesNotGetInherited
- base
- base
- SuppressWarnings
- Retention
- Target
- Feature
- testCustomAnnotationRestriction
- checkTestAnnotationFoo
- checkJavascriptInterfaceFoo
- Feature
- testAddJavascriptInterfaceIsSafeByDefault
- blocked
- allowed
- Feature
- testObjectsInspection
- m1
- m2
- m2
- Feature
- testAccessToObjectGetClassIsBlocked
- executeJavaScriptAndWaitForExceptionSynchronously
package org.chromium.content.browser;
import android.os.Handler;
import android.os.Looper;
import android.test.suitebuilder.annotation.SmallTest;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
import org.chromium.content_shell_apk.ContentShellActivity;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.ref.WeakReference;
public class JavaBridgeBasicsTest extends JavaBridgeTestBase {
private class TestController extends Controller {
private int mIntValue;
private long mLongValue;
private String mStringValue;
private boolean mBooleanValue;
public synchronized void setIntValue(int x) {
mIntValue = x;
notifyResultIsReady();
}
public synchronized void setLongValue(long x) {
mLongValue = x;
notifyResultIsReady();
}
public synchronized void setStringValue(String x) {
mStringValue = x;
notifyResultIsReady();
}
public synchronized void setBooleanValue(boolean x) {
mBooleanValue = x;
notifyResultIsReady();
}
public synchronized int waitForIntValue() {
waitForResult();
return mIntValue;
}
public synchronized long waitForLongValue() {
waitForResult();
return mLongValue;
}
public synchronized String waitForStringValue() {
waitForResult();
return mStringValue;
}
public synchronized boolean waitForBooleanValue() {
waitForResult();
return mBooleanValue;
}
public synchronized String getStringValue() {
return mStringValue;
}
}
private static class ObjectWithStaticMethod {
public static String staticMethod() {
return "foo";
}
}
TestController mTestController;
@Override
protected void setUp() throws Exception {
super.setUp();
mTestController = new TestController();
setUpContentView(mTestController, "testController");
}
@Override
protected ContentShellActivity launchContentShellWithUrl(String url) {
return launchContentShellWithUrlAndCommandLineArgs(
url, new String[]{ "--js-flags=--expose-gc" });
}
protected String executeJavaScriptAndGetStringResult(String script) throws Throwable {
executeJavaScript("testController.setStringValue(" + script + ");");
return mTestController.waitForStringValue();
}
protected void injectObjectAndReload(final Object object, final String name) throws Throwable {
injectObjectAndReload(object, name, null);
}
protected void injectObjectAndReload(final Object object, final String name,
final Class<? extends Annotation> requiredAnnotation) throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mTestCallbackHelperContainer.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(object,
name, requiredAnnotation);
getContentView().getContentViewCore().reload(true);
}
});
onPageFinishedHelper.waitForCallback(currentCallCount);
}
protected void synchronousPageReload() throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mTestCallbackHelperContainer.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().reload(true);
}
});
onPageFinishedHelper.waitForCallback(currentCallCount);
}
private void assertRaisesException(String script) throws Throwable {
executeJavaScript("try {" +
script + ";" +
" testController.setBooleanValue(false);" +
"} catch (exception) {" +
" testController.setBooleanValue(true);" +
"}");
assertTrue(mTestController.waitForBooleanValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testTypeOfInjectedObject() throws Throwable {
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testAdditionNotReflectedUntilReload() throws Throwable {
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
new Object(), "testObject", null);
}
});
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
synchronousPageReload();
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testRemovalNotReflectedUntilReload() throws Throwable {
injectObjectAndReload(new Object(), "testObject");
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().removeJavascriptInterface("testObject");
}
});
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
synchronousPageReload();
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testRemoveObjectNotAdded() throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mTestCallbackHelperContainer.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().removeJavascriptInterface("foo");
getContentView().getContentViewCore().reload(true);
}
});
onPageFinishedHelper.waitForCallback(currentCallCount);
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof foo"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testTypeOfMethod() throws Throwable {
assertEquals("function",
executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testTypeOfInvalidMethod() throws Throwable {
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testCallingInvalidMethodRaisesException() throws Throwable {
assertRaisesException("testController.foo()");
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testUncaughtJavaExceptionRaisesJavaScriptException() throws Throwable {
injectObjectAndReload(new Object() {
public void method() { throw new RuntimeException("foo"); }
}, "testObject");
assertRaisesException("testObject.method()");
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testTypeOfStaticMethod() throws Throwable {
injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
executeJavaScript("testController.setStringValue(typeof testObject.staticMethod)");
assertEquals("function", mTestController.waitForStringValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testCallStaticMethod() throws Throwable {
injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
executeJavaScript("testController.setStringValue(testObject.staticMethod())");
assertEquals("foo", mTestController.waitForStringValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testPrivateMethodNotExposed() throws Throwable {
injectObjectAndReload(new Object() {
private void method() {}
protected void method2() {}
}, "testObject");
assertEquals("undefined",
executeJavaScriptAndGetStringResult("typeof testObject.method"));
assertEquals("undefined",
executeJavaScriptAndGetStringResult("typeof testObject.method2"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReplaceInjectedObject() throws Throwable {
injectObjectAndReload(new Object() {
public void method() { mTestController.setStringValue("object 1"); }
}, "testObject");
executeJavaScript("testObject.method()");
assertEquals("object 1", mTestController.waitForStringValue());
injectObjectAndReload(new Object() {
public void method() { mTestController.setStringValue("object 2"); }
}, "testObject");
executeJavaScript("testObject.method()");
assertEquals("object 2", mTestController.waitForStringValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testInjectNullObjectIsIgnored() throws Throwable {
injectObjectAndReload(null, "testObject");
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable {
injectObjectAndReload(new Object(), "testObject");
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
injectObjectAndReload(null, "testObject");
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable {
injectObjectAndReload(new Object() {
public void method() { mTestController.setStringValue("0 args"); }
public void method(int x) { mTestController.setStringValue("1 arg"); }
public void method(int x, int y) { mTestController.setStringValue("2 args"); }
}, "testObject");
executeJavaScript("testObject.method()");
assertEquals("0 args", mTestController.waitForStringValue());
executeJavaScript("testObject.method(42)");
assertEquals("1 arg", mTestController.waitForStringValue());
executeJavaScript("testObject.method(null)");
assertEquals("1 arg", mTestController.waitForStringValue());
executeJavaScript("testObject.method(undefined)");
assertEquals("1 arg", mTestController.waitForStringValue());
executeJavaScript("testObject.method(42, 42)");
assertEquals("2 args", mTestController.waitForStringValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
assertRaisesException("testController.setIntValue()");
assertRaisesException("testController.setIntValue(42, 42)");
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testObjectPersistsAcrossPageLoads() throws Throwable {
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
synchronousPageReload();
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testClientPropertiesPersistAcrossPageLoads() throws Throwable {
assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
executeJavaScript("testController.myProperty = 42;");
assertEquals("42", executeJavaScriptAndGetStringResult("testController.myProperty"));
synchronousPageReload();
assertEquals("42", executeJavaScriptAndGetStringResult("testController.myProperty"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testSameObjectInjectedMultipleTimes() throws Throwable {
class TestObject {
private int mNumMethodInvocations;
public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
}
final TestObject testObject = new TestObject();
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mTestCallbackHelperContainer.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
testObject, "testObject1", null);
getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
testObject, "testObject2", null);
getContentView().getContentViewCore().reload(true);
}
});
onPageFinishedHelper.waitForCallback(currentCallCount);
executeJavaScript("testObject1.method()");
assertEquals(1, mTestController.waitForIntValue());
executeJavaScript("testObject2.method()");
assertEquals(2, mTestController.waitForIntValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testCallMethodOnReturnedObject() throws Throwable {
injectObjectAndReload(new Object() {
public Object getInnerObject() {
return new Object() {
public void method(int x) { mTestController.setIntValue(x); }
};
}
}, "testObject");
executeJavaScript("testObject.getInnerObject().method(42)");
assertEquals(42, mTestController.waitForIntValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReturnedObjectInjectedElsewhere() throws Throwable {
class InnerObject {
private int mNumMethodInvocations;
public void method() { mTestController.setIntValue(++mNumMethodInvocations); }
}
final InnerObject innerObject = new InnerObject();
final Object object = new Object() {
public InnerObject getInnerObject() {
return innerObject;
}
};
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mTestCallbackHelperContainer.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
object, "testObject", null);
getContentView().getContentViewCore().addPossiblyUnsafeJavascriptInterface(
innerObject, "innerObject", null);
getContentView().getContentViewCore().reload(true);
}
});
onPageFinishedHelper.waitForCallback(currentCallCount);
executeJavaScript("testObject.getInnerObject().method()");
assertEquals(1, mTestController.waitForIntValue());
executeJavaScript("innerObject.method()");
assertEquals(2, mTestController.waitForIntValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReturnedObjectIsGarbageCollected() throws Throwable {
assertEquals("function", executeJavaScriptAndGetStringResult("typeof gc"));
class InnerObject {
}
class TestObject {
public InnerObject getInnerObject() {
InnerObject inner = new InnerObject();
weakRefForInner = new WeakReference<InnerObject>(inner);
return inner;
}
WeakReference<InnerObject> weakRefForInner;
}
TestObject object = new TestObject();
injectObjectAndReload(object, "testObject");
assertEquals("object", executeJavaScriptAndGetStringResult(
"(function() { " +
"globalInner = testObject.getInnerObject(); return typeof globalInner; " +
"})()"));
assertTrue(object.weakRefForInner.get() != null);
Runtime.getRuntime().gc();
assertTrue(object.weakRefForInner.get() != null);
assertEquals("true", executeJavaScriptAndGetStringResult(
"(function() { " +
"delete globalInner; gc(); return (typeof globalInner == 'undefined'); " +
"})()"));
Runtime.getRuntime().gc();
assertEquals(null, object.weakRefForInner.get());
}
@DisabledTest
public void testSameReturnedObjectUsesSameWrapper() throws Throwable {
class InnerObject {
}
final InnerObject innerObject = new InnerObject();
final Object injectedTestObject = new Object() {
public InnerObject getInnerObject() {
return innerObject;
}
};
injectObjectAndReload(injectedTestObject, "injectedTestObject");
executeJavaScript("inner1 = injectedTestObject.getInnerObject()");
executeJavaScript("inner2 = injectedTestObject.getInnerObject()");
assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner1"));
assertEquals("object", executeJavaScriptAndGetStringResult("typeof inner2"));
assertEquals("true", executeJavaScriptAndGetStringResult("inner1 === inner2"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testMethodInvokedOnBackgroundThread() throws Throwable {
injectObjectAndReload(new Object() {
public void captureThreadId() {
mTestController.setLongValue(Thread.currentThread().getId());
}
}, "testObject");
executeJavaScript("testObject.captureThreadId()");
final long threadId = mTestController.waitForLongValue();
assertFalse(threadId == Thread.currentThread().getId());
runTestOnUiThread(new Runnable() {
@Override
public void run() {
assertFalse(threadId == Thread.currentThread().getId());
}
});
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testPublicInheritedMethod() throws Throwable {
class Base {
public void method(int x) { mTestController.setIntValue(x); }
}
class Derived extends Base {
}
injectObjectAndReload(new Derived(), "testObject");
assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.method"));
executeJavaScript("testObject.method(42)");
assertEquals(42, mTestController.waitForIntValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testPrivateInheritedMethod() throws Throwable {
class Base {
private void method() {}
}
class Derived extends Base {
}
injectObjectAndReload(new Derived(), "testObject");
assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject.method"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testOverriddenMethod() throws Throwable {
class Base {
public void method() { mTestController.setStringValue("base"); }
}
class Derived extends Base {
@Override
public void method() { mTestController.setStringValue("derived"); }
}
injectObjectAndReload(new Derived(), "testObject");
executeJavaScript("testObject.method()");
assertEquals("derived", mTestController.waitForStringValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testEnumerateMembers() throws Throwable {
injectObjectAndReload(new Object() {
public void method() {}
private void privateMethod() {}
public int field;
private int privateField;
}, "testObject");
executeJavaScript(
"var result = \"\"; " +
"for (x in testObject) { result += \" \" + x } " +
"testController.setStringValue(result);");
assertEquals(" equals getClass hashCode method notify notifyAll toString wait",
mTestController.waitForStringValue());
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReflectPublicMethod() throws Throwable {
injectObjectAndReload(new Object() {
public Class<?> myGetClass() { return getClass(); }
public String method() { return "foo"; }
}, "testObject");
assertEquals("foo", executeJavaScriptAndGetStringResult(
"testObject.myGetClass().getMethod('method', null).invoke(testObject, null)" +
".toString()"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReflectPublicField() throws Throwable {
injectObjectAndReload(new Object() {
public Class<?> myGetClass() { return getClass(); }
public String field = "foo";
}, "testObject");
assertEquals("foo", executeJavaScriptAndGetStringResult(
"testObject.myGetClass().getField('field').get(testObject).toString()"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReflectPrivateMethodRaisesException() throws Throwable {
injectObjectAndReload(new Object() {
public Class<?> myGetClass() { return getClass(); }
private void method() {};
}, "testObject");
assertRaisesException("testObject.myGetClass().getMethod('method', null)");
assertRaisesException(
"testObject.myGetClass().getDeclaredMethod('method', null)." +
"invoke(testObject, null)");
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testReflectPrivateFieldRaisesException() throws Throwable {
injectObjectAndReload(new Object() {
public Class<?> myGetClass() { return getClass(); }
private int field;
}, "testObject");
assertRaisesException("testObject.myGetClass().getField('field')");
assertRaisesException(
"testObject.myGetClass().getDeclaredField('field').getInt(testObject)");
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testAllowNonAnnotatedMethods() throws Throwable {
injectObjectAndReload(new Object() {
public String allowed() { return "foo"; }
}, "testObject", null);
assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
assertEquals("string", executeJavaScriptAndGetStringResult("typeof testObject.toString()"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testAllowOnlyAnnotatedMethods() throws Throwable {
injectObjectAndReload(new Object() {
@JavascriptInterface
public String allowed() { return "foo"; }
public String disallowed() { return "bar"; }
}, "testObject", JavascriptInterface.class);
assertRaisesException("testObject.getClass()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof testObject.getClass"));
assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()"));
assertRaisesException("testObject.disallowed()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof testObject.disallowed"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testAnnotationRequirementRetainsPropertyAcrossObjects() throws Throwable {
class Test {
@JavascriptInterface
public String safe() { return "foo"; }
public String unsafe() { return "bar"; }
}
class TestReturner {
@JavascriptInterface
public Test getTest() { return new Test(); }
}
injectObjectAndReload(new TestReturner(), "unsafeTestObject", null);
assertEquals("foo", executeJavaScriptAndGetStringResult(
"unsafeTestObject.getTest().safe()"));
assertEquals("bar", executeJavaScriptAndGetStringResult(
"unsafeTestObject.getTest().unsafe()"));
injectObjectAndReload(new TestReturner(), "safeTestObject", JavascriptInterface.class);
assertEquals("foo", executeJavaScriptAndGetStringResult(
"safeTestObject.getTest().safe()"));
assertRaisesException("safeTestObject.getTest().unsafe()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof safeTestObject.getTest().unsafe"));
assertRaisesException("safeTestObject.getTest().getClass()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof safeTestObject.getTest().getClass"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testAnnotationDoesNotGetInherited() throws Throwable {
class Base {
@JavascriptInterface
public void base() { }
}
class Child extends Base {
@Override
public void base() { }
}
injectObjectAndReload(new Child(), "testObject", JavascriptInterface.class);
assertRaisesException("testObject.base()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof testObject.base"));
}
@SuppressWarnings("javadoc")
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@interface TestAnnotation {
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testCustomAnnotationRestriction() throws Throwable {
class Test {
@TestAnnotation
public String checkTestAnnotationFoo() { return "bar"; }
@JavascriptInterface
public String checkJavascriptInterfaceFoo() { return "bar"; }
}
injectObjectAndReload(new Test(), "javascriptInterfaceObj", JavascriptInterface.class);
assertRaisesException("javascriptInterfaceObj.checkTestAnnotationFoo()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof javascriptInterfaceObj.checkTestAnnotationFoo"));
assertEquals("bar", executeJavaScriptAndGetStringResult(
"javascriptInterfaceObj.checkJavascriptInterfaceFoo()"));
injectObjectAndReload(new Test(), "testAnnotationObj", TestAnnotation.class);
assertEquals("bar", executeJavaScriptAndGetStringResult(
"testAnnotationObj.checkTestAnnotationFoo()"));
assertRaisesException("testAnnotationObj.checkJavascriptInterfaceFoo()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof testAnnotationObj.checkJavascriptInterfaceFoo"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testAddJavascriptInterfaceIsSafeByDefault() throws Throwable {
class Test {
public String blocked() { return "bar"; }
@JavascriptInterface
public String allowed() { return "bar"; }
}
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mTestCallbackHelperContainer.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().addJavascriptInterface(new Test(),
"testObject");
getContentView().getContentViewCore().reload(true);
}
});
onPageFinishedHelper.waitForCallback(currentCallCount);
assertEquals("bar", executeJavaScriptAndGetStringResult(
"testObject.allowed()"));
assertRaisesException("testObject.blocked()");
assertEquals("undefined", executeJavaScriptAndGetStringResult(
"typeof testObject.blocked"));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testObjectsInspection() throws Throwable {
class Test {
@JavascriptInterface
public String m1() { return "foo"; }
@JavascriptInterface
public String m2() { return "bar"; }
@JavascriptInterface
public String m2(int x) { return "bar " + x; }
}
final String jsObjectKeysTestTemplate = "Object.keys(%s).toString()";
final String jsForInTestTemplate =
"(function(){" +
" var s=[]; for(var m in %s) s.push(m); return s.join(\",\")" +
"})()";
final String inspectableObjectName = "testObj1";
final String nonInspectableObjectName = "testObj2";
injectObjectAndReload(new Test(), inspectableObjectName, JavascriptInterface.class);
assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
String.format(jsObjectKeysTestTemplate, inspectableObjectName)));
assertEquals("m1,m2", executeJavaScriptAndGetStringResult(
String.format(jsForInTestTemplate, inspectableObjectName)));
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().getContentViewCore().setAllowJavascriptInterfacesInspection(false);
}
});
injectObjectAndReload(new Test(), nonInspectableObjectName, JavascriptInterface.class);
assertEquals("", executeJavaScriptAndGetStringResult(
String.format(jsObjectKeysTestTemplate, nonInspectableObjectName)));
assertEquals("", executeJavaScriptAndGetStringResult(
String.format(jsForInTestTemplate, nonInspectableObjectName)));
}
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
public void testAccessToObjectGetClassIsBlocked() throws Throwable {
injectObjectAndReload(new Object(), "testObject");
assertEquals("function", executeJavaScriptAndGetStringResult("typeof testObject.getClass"));
boolean securityExceptionThrown = false;
try {
final String result = executeJavaScriptAndWaitForExceptionSynchronously(
"typeof testObject.getClass()");
fail("A call to java.lang.Object.getClass has been allowed, result: '" + result + "'");
} catch (SecurityException exception) {
securityExceptionThrown = true;
}
assertTrue(securityExceptionThrown);
}
private String executeJavaScriptAndWaitForExceptionSynchronously(final String script)
throws Throwable {
class ExitLoopException extends RuntimeException {
}
mTestController.setStringValue(null);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
getContentView().loadUrl(new LoadUrlParams("javascript:(function() { " +
"testController.setStringValue(" + script + ") })()"));
do {
final Boolean[] deactivateExitLoopTask = new Boolean[1];
deactivateExitLoopTask[0] = false;
new Handler(Looper.myLooper()).post(new Runnable() {
@Override
public void run() {
if (!deactivateExitLoopTask[0]) {
throw new ExitLoopException();
}
}
});
try {
Looper.loop();
} catch (ExitLoopException e) {
} catch (RuntimeException e) {
deactivateExitLoopTask[0] = true;
throw e;
}
} while (mTestController.getStringValue() == null ||
mTestController.getStringValue().equals("null"));
}
});
return mTestController.getStringValue();
}
}