Index: content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java |
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java |
index 88c4b3b742f8aad08a5eb0be4bb3b82bc7d45b1c..27cf1d6181531b96f3fe52e87a6f11e41bcc4221 100644 |
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java |
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java |
@@ -4,6 +4,8 @@ |
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; |
@@ -70,6 +72,10 @@ public class JavaBridgeBasicsTest extends JavaBridgeTestBase { |
waitForResult(); |
return mBooleanValue; |
} |
+ |
+ public synchronized String getStringValue() { |
+ return mStringValue; |
+ } |
} |
private static class ObjectWithStaticMethod { |
@@ -566,10 +572,11 @@ public class JavaBridgeBasicsTest extends JavaBridgeTestBase { |
@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.getClass().getMethod('method', null).invoke(testObject, null)" + |
+ "testObject.myGetClass().getMethod('method', null).invoke(testObject, null)" + |
".toString()")); |
} |
@@ -577,36 +584,40 @@ public class JavaBridgeBasicsTest extends JavaBridgeTestBase { |
@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.getClass().getField('field').get(testObject).toString()")); |
+ "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.getClass().getMethod('method', null)"); |
+ assertRaisesException("testObject.myGetClass().getMethod('method', null)"); |
// getDeclaredMethod() is able to access a private method, but invoke() |
// throws a Java exception. |
assertRaisesException( |
- "testObject.getClass().getDeclaredMethod('method', null).invoke(testObject, null)"); |
+ "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.getClass().getField('field')"); |
+ assertRaisesException("testObject.myGetClass().getField('field')"); |
// getDeclaredField() is able to access a private field, but getInt() |
// throws a Java exception. |
assertRaisesException( |
- "testObject.getClass().getDeclaredField('field').getInt(testObject)"); |
+ "testObject.myGetClass().getDeclaredField('field').getInt(testObject)"); |
} |
@SmallTest |
@@ -619,8 +630,8 @@ public class JavaBridgeBasicsTest extends JavaBridgeTestBase { |
// Test calling a method of an explicitly inherited class (Base#allowed()). |
assertEquals("foo", executeJavaScriptAndGetStringResult("testObject.allowed()")); |
- // Test calling a method of an implicitly inherited class (Object#getClass()). |
- assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject.getClass()")); |
+ // Test calling a method of an implicitly inherited class (Object#toString()). |
+ assertEquals("string", executeJavaScriptAndGetStringResult("typeof testObject.toString()")); |
} |
@SmallTest |
@@ -835,4 +846,66 @@ public class JavaBridgeBasicsTest extends JavaBridgeTestBase { |
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); |
+ } |
+ |
+ // Unlike executeJavaScriptAndGetStringResult, this method is sitting on the UI thread |
+ // until a non-null result is obtained or a Java exception has been thrown. This method is |
+ // capable of catching Java RuntimeExceptions happening on the UI thread asynchronously. |
+ 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; |
+ // We can't use Loop.quit(), as this is the main looper, so we throw |
+ // an exception to bail out from the loop. |
+ new Handler(Looper.myLooper()).post(new Runnable() { |
+ @Override |
+ public void run() { |
+ if (!deactivateExitLoopTask[0]) { |
+ throw new ExitLoopException(); |
+ } |
+ } |
+ }); |
+ try { |
+ Looper.loop(); |
+ } catch (ExitLoopException e) { |
+ // Intentionally empty. |
+ } catch (RuntimeException e) { |
+ // Prevent the task that throws the ExitLoopException from exploding |
+ // on the main loop outside of this function. |
+ deactivateExitLoopTask[0] = true; |
+ throw e; |
+ } |
+ } while (mTestController.getStringValue() == null || |
+ // When an exception in an injected method happens, the function returns |
+ // null. We ignore this and wait until the exception on the browser side |
+ // will be thrown. |
+ mTestController.getStringValue().equals("null")); |
+ } |
+ }); |
+ return mTestController.getStringValue(); |
+ } |
} |