summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail Naganov <mnaganov@google.com>2014-12-09 13:56:30 +0000
committerMikhail Naganov <mnaganov@google.com>2014-12-09 13:56:30 +0000
commitc64ad5c2e063cde757eba7f9499a8e748b57cbdc (patch)
tree513e2ec7ee5c0eda3e0f801a6c69cb6879fc5bfe
parent64a92734073d2aa3acb7fc4b0dc6ab78963012fc (diff)
downloadchromium_org-c64ad5c2e063cde757eba7f9499a8e748b57cbdc.tar.gz
Cherry-pick: [Android] Java Bridge: handle requests from Java Script on the background thread
Same as the upsteam patch, with purely syntactic adaptations of "override" keywords usage to the legacy style used in M39. Bug: 18520475 Original description: commit a4b0d4cc0b9780aecd3e23a94293b2e3002baf7e Author: mnaganov <mnaganov@chromium.org> Date: Tue Dec 09 13:30:08 2014 [Android] Java Bridge: handle requests from Java Script on the background thread This allows the page code that calls into injected objects methods to run even if the browser UI thread is blocked (in WebView, the browser UI thread is the main app thread, and sometimes people use synchronization primitives there that wait on some action performed by the injected object, see the bug for an example). In this CL, we make the minimal required amount of changes in order to simplify cherry-picking into older branches. A cleanup CL (or CLs) will follow. High-level description of changes: 1. We intercept messages from renderers on the IO thread using BrowserMessageFilter and handle Java Bridge messages entirely on the background thread. This implies the following changes: 2. GinJavaBridgeDispatcherHost has become RefCountedThreadSafe (via BrowserMessageFilter) 3. We have to use route IDs of RenderFrameHosts instead of pointers to them, as we can't access RFHs from IO or background threads. 4. Objects registry is now accessed from UI and background threads, and we have to use locking. We can't anymore restrict access to the registry to a single thread. This also means we can't anymore implement the registry using IDMap, as it is explicitly non-thread-safe. 5. A lot of code was removed, as we now serve synchronous requests from renderers without inter-thread hopping! 6. Of course, a new regression test has been added that now passes. BUG=438255 Review URL: https://codereview.chromium.org/772123002 Change-Id: I9e517024878f0b95a32f95524de4f8017ba8dc21 Cr-Commit-Position: refs/heads/master@{#307460}
-rw-r--r--android_webview/javatests/src/org/chromium/android_webview/test/AwJavaBridgeTest.java100
-rw-r--r--content/browser/android/content_view_core_impl.cc4
-rw-r--r--content/browser/android/content_view_core_impl.h3
-rw-r--r--content/browser/android/java/gin_java_bound_object.cc6
-rw-r--r--content/browser/android/java/gin_java_bound_object.h22
-rw-r--r--content/browser/android/java/gin_java_bridge_dispatcher_host.cc532
-rw-r--r--content/browser/android/java/gin_java_bridge_dispatcher_host.h107
-rw-r--r--content/browser/android/java/gin_java_method_invocation_helper.h10
-rw-r--r--content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java47
9 files changed, 444 insertions, 387 deletions
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwJavaBridgeTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwJavaBridgeTest.java
index 253a361aa3..e39252f911 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwJavaBridgeTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwJavaBridgeTest.java
@@ -43,9 +43,9 @@ public class AwJavaBridgeTest extends AwTestBase {
}
});
// Destroying one AwContents from within the JS callback should still
- // leave others functioning.
- loadDataSync(view2.getAwContents(), client2.getOnPageFinishedHelper(),
- html, "text/html", false);
+ // leave others functioning. Note that we must do this asynchronously,
+ // as Blink thread is currently blocked waiting for this method to finish.
+ loadDataAsync(view2.getAwContents(), html, "text/html", false);
} catch (Throwable t) {
throw new RuntimeException(t);
}
@@ -66,9 +66,99 @@ public class AwJavaBridgeTest extends AwTestBase {
// Ensure the JS interface object is there, and invoke the test method.
assertEquals("\"function\"", executeJavaScriptAndWaitForResult(
awContents, mContentsClient, "typeof test.destroy"));
+ int currentCallCount = client2.getOnPageFinishedHelper().getCallCount();
awContents.evaluateJavaScript("test.destroy()", null);
- client2.getOnPageFinishedHelper().waitForCallback(
- client2.getOnPageFinishedHelper().getCallCount());
+ client2.getOnPageFinishedHelper().waitForCallback(currentCallCount);
+ }
+
+ @SmallTest
+ @Feature({"AndroidWebView", "Android-JavaBridge"})
+ public void testTwoWebViewsCreatedSimultaneously() throws Throwable {
+ final AwContents awContents1 = mTestContainerView.getAwContents();
+ final TestAwContentsClient client2 = new TestAwContentsClient();
+ final AwTestContainerView view2 = createAwTestContainerViewOnMainSync(client2);
+ final AwContents awContents2 = view2.getAwContents();
+
+ enableJavaScriptOnUiThread(awContents1);
+ enableJavaScriptOnUiThread(awContents2);
+
+ class Test {
+ Test(int value) {
+ mValue = value;
+ }
+ @JavascriptInterface
+ public int getValue() {
+ return mValue;
+ }
+ private int mValue;
+ }
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ awContents1.addPossiblyUnsafeJavascriptInterface(new Test(1), "test", null);
+ awContents2.addPossiblyUnsafeJavascriptInterface(new Test(2), "test", null);
+ }
+ });
+ final String html = "<html>Hello World</html>";
+ loadDataSync(awContents1, mContentsClient.getOnPageFinishedHelper(), html,
+ "text/html", false);
+ loadDataSync(awContents2, client2.getOnPageFinishedHelper(), html,
+ "text/html", false);
+
+ assertEquals("1",
+ executeJavaScriptAndWaitForResult(awContents1, mContentsClient, "test.getValue()"));
+ assertEquals("2",
+ executeJavaScriptAndWaitForResult(awContents2, client2, "test.getValue()"));
+ }
+
+ @SmallTest
+ @Feature({"AndroidWebView", "Android-JavaBridge"})
+ public void testTwoWebViewsSecondCreatedAfterLoadingInFirst() throws Throwable {
+ final AwContents awContents1 = mTestContainerView.getAwContents();
+ enableJavaScriptOnUiThread(awContents1);
+
+ class Test {
+ Test(int value) {
+ mValue = value;
+ }
+ @JavascriptInterface
+ public int getValue() {
+ return mValue;
+ }
+ private int mValue;
+ }
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ awContents1.addPossiblyUnsafeJavascriptInterface(new Test(1), "test", null);
+ }
+ });
+ final String html = "<html>Hello World</html>";
+ loadDataSync(awContents1, mContentsClient.getOnPageFinishedHelper(), html,
+ "text/html", false);
+ assertEquals("1",
+ executeJavaScriptAndWaitForResult(awContents1, mContentsClient, "test.getValue()"));
+
+ final TestAwContentsClient client2 = new TestAwContentsClient();
+ final AwTestContainerView view2 = createAwTestContainerViewOnMainSync(client2);
+ final AwContents awContents2 = view2.getAwContents();
+ enableJavaScriptOnUiThread(awContents2);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ awContents2.addPossiblyUnsafeJavascriptInterface(new Test(2), "test", null);
+ }
+ });
+ loadDataSync(awContents2, client2.getOnPageFinishedHelper(), html,
+ "text/html", false);
+
+ assertEquals("1",
+ executeJavaScriptAndWaitForResult(awContents1, mContentsClient, "test.getValue()"));
+ assertEquals("2",
+ executeJavaScriptAndWaitForResult(awContents2, client2, "test.getValue()"));
}
}
diff --git a/content/browser/android/content_view_core_impl.cc b/content/browser/android/content_view_core_impl.cc
index fece49f90f..5f7a93abc9 100644
--- a/content/browser/android/content_view_core_impl.cc
+++ b/content/browser/android/content_view_core_impl.cc
@@ -242,9 +242,9 @@ ContentViewCoreImpl::ContentViewCoreImpl(
BuildUserAgentFromOSAndProduct(kLinuxInfoStr, product);
web_contents->SetUserAgentOverride(spoofed_ua);
- java_bridge_dispatcher_host_.reset(
+ java_bridge_dispatcher_host_ =
new GinJavaBridgeDispatcherHost(web_contents,
- java_bridge_retained_object_set));
+ java_bridge_retained_object_set);
InitWebContents();
}
diff --git a/content/browser/android/content_view_core_impl.h b/content/browser/android/content_view_core_impl.h
index d7f2711c10..1fdd9f595e 100644
--- a/content/browser/android/content_view_core_impl.h
+++ b/content/browser/android/content_view_core_impl.h
@@ -343,8 +343,7 @@ class ContentViewCoreImpl : public ContentViewCore,
bool accessibility_enabled_;
// Manages injecting Java objects.
- scoped_ptr<GinJavaBridgeDispatcherHost>
- java_bridge_dispatcher_host_;
+ scoped_refptr<GinJavaBridgeDispatcherHost> java_bridge_dispatcher_host_;
DISALLOW_COPY_AND_ASSIGN(ContentViewCoreImpl);
};
diff --git a/content/browser/android/java/gin_java_bound_object.cc b/content/browser/android/java/gin_java_bound_object.cc
index 0440136ed5..6423c43a44 100644
--- a/content/browser/android/java/gin_java_bound_object.cc
+++ b/content/browser/android/java/gin_java_bound_object.cc
@@ -42,8 +42,8 @@ GinJavaBoundObject* GinJavaBoundObject::CreateNamed(
GinJavaBoundObject* GinJavaBoundObject::CreateTransient(
const JavaObjectWeakGlobalRef& ref,
const base::android::JavaRef<jclass>& safe_annotation_clazz,
- RenderFrameHost* holder) {
- std::set<RenderFrameHost*> holders;
+ int32 holder) {
+ std::set<int32> holders;
holders.insert(holder);
return new GinJavaBoundObject(ref, safe_annotation_clazz, holders);
}
@@ -61,7 +61,7 @@ GinJavaBoundObject::GinJavaBoundObject(
GinJavaBoundObject::GinJavaBoundObject(
const JavaObjectWeakGlobalRef& ref,
const base::android::JavaRef<jclass>& safe_annotation_clazz,
- const std::set<RenderFrameHost*> holders)
+ const std::set<int32>& holders)
: ref_(ref),
names_count_(0),
holders_(holders),
diff --git a/content/browser/android/java/gin_java_bound_object.h b/content/browser/android/java/gin_java_bound_object.h
index ce1d403e7f..72fa7c6f8e 100644
--- a/content/browser/android/java/gin_java_bound_object.h
+++ b/content/browser/android/java/gin_java_bound_object.h
@@ -10,26 +10,17 @@
#include "base/android/jni_weak_ref.h"
#include "base/android/scoped_java_ref.h"
-#include "base/id_map.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "content/browser/android/java/java_method.h"
namespace content {
-class RenderFrameHost;
-
class GinJavaBoundObject
: public base::RefCountedThreadSafe<GinJavaBoundObject> {
public:
- // Using scoped_refptr<> as a value type is somewhat not IDMap had been
- // designed for (it returns T* from lookups, so we have to de-ref it), but on
- // the other hand, this allows us to manage lifetime of GinJavaBoundObject
- // automatically.
- typedef IDMap<scoped_refptr<GinJavaBoundObject>, IDMapOwnPointer> ObjectMap;
- typedef ObjectMap::KeyType ObjectID;
+ typedef int32 ObjectID;
static GinJavaBoundObject* CreateNamed(
const JavaObjectWeakGlobalRef& ref,
@@ -37,8 +28,9 @@ class GinJavaBoundObject
static GinJavaBoundObject* CreateTransient(
const JavaObjectWeakGlobalRef& ref,
const base::android::JavaRef<jclass>& safe_annotation_clazz,
- RenderFrameHost* holder);
+ int32 holder);
+ // The following methods can be called on any thread.
JavaObjectWeakGlobalRef& GetWeakRef() { return ref_; }
base::android::ScopedJavaLocalRef<jobject> GetLocalRef(JNIEnv* env) {
return ref_.get(env);
@@ -49,8 +41,8 @@ class GinJavaBoundObject
void RemoveName() { --names_count_; }
bool HasHolders() { return !holders_.empty(); }
- void AddHolder(RenderFrameHost* holder) { holders_.insert(holder); }
- void RemoveHolder(RenderFrameHost* holder) { holders_.erase(holder); }
+ void AddHolder(int32 holder) { holders_.insert(holder); }
+ void RemoveHolder(int32 holder) { holders_.erase(holder); }
// The following methods are called on the background thread.
std::set<std::string> GetMethodNames();
@@ -70,7 +62,7 @@ class GinJavaBoundObject
GinJavaBoundObject(
const JavaObjectWeakGlobalRef& ref,
const base::android::JavaRef<jclass>& safe_annotation_clazz,
- const std::set<RenderFrameHost*> holders);
+ const std::set<int32>& holders);
~GinJavaBoundObject();
// The following methods are called on the background thread.
@@ -81,7 +73,7 @@ class GinJavaBoundObject
// An object must be kept in retained_object_set_ either if it has
// names or if it has a non-empty holders set.
int names_count_;
- std::set<RenderFrameHost*> holders_;
+ std::set<int32> holders_;
// The following fields are accessed on the background thread.
typedef std::multimap<std::string, linked_ptr<JavaMethod> > JavaMethodMap;
diff --git a/content/browser/android/java/gin_java_bridge_dispatcher_host.cc b/content/browser/android/java/gin_java_bridge_dispatcher_host.cc
index f4f1e20e2e..3e44a01fc7 100644
--- a/content/browser/android/java/gin_java_bridge_dispatcher_host.cc
+++ b/content/browser/android/java/gin_java_bridge_dispatcher_host.cc
@@ -7,7 +7,9 @@
#include "base/android/java_handler_thread.h"
#include "base/android/jni_android.h"
#include "base/android/scoped_java_ref.h"
+#include "base/atomic_sequence_num.h"
#include "base/lazy_instance.h"
+#include "base/pickle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
@@ -18,6 +20,7 @@
#include "content/common/gin_java_bridge_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "ipc/ipc_message_utils.h"
@@ -38,29 +41,63 @@ class JavaBridgeThread : public base::android::JavaHandlerThread {
virtual ~JavaBridgeThread() {
Stop();
}
+ static bool CurrentlyOn();
};
base::LazyInstance<JavaBridgeThread> g_background_thread =
LAZY_INSTANCE_INITIALIZER;
+// static
+bool JavaBridgeThread::CurrentlyOn() {
+ return base::MessageLoop::current() ==
+ g_background_thread.Get().message_loop();
+}
+
+// Object IDs are globally unique, so we can figure out the right
+// GinJavaBridgeDispatcherHost when dispatching messages on the background
+// thread.
+base::StaticAtomicSequenceNumber g_next_object_id;
+
} // namespace
GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost(
WebContents* web_contents,
jobject retained_object_set)
: WebContentsObserver(web_contents),
+ BrowserMessageFilter(GinJavaBridgeMsgStart),
+ browser_filter_added_(false),
retained_object_set_(base::android::AttachCurrentThread(),
retained_object_set),
- allow_object_contents_inspection_(true) {
+ allow_object_contents_inspection_(true),
+ current_routing_id_(MSG_ROUTING_NONE) {
DCHECK(retained_object_set);
}
GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
- DCHECK(pending_replies_.empty());
+}
+
+// GinJavaBridgeDispatcherHost gets created earlier than RenderProcessHost
+// is initialized. So we postpone installing the message filter until we know
+// that the RPH is in a good shape. Currently this means that we are calling
+// this function from any UI thread function that is about to communicate
+// with the renderer.
+// TODO(mnaganov): Redesign, so we only have a single filter for all hosts.
+void GinJavaBridgeDispatcherHost::AddBrowserFilterIfNeeded() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // Transient objects can only appear after named objects were added. Thus,
+ // we can wait until we have one, to avoid installing unnecessary filters.
+ if (!browser_filter_added_ &&
+ web_contents()->GetRenderProcessHost()->GetChannel() &&
+ !named_objects_.empty()) {
+ web_contents()->GetRenderProcessHost()->AddFilter(this);
+ browser_filter_added_ = true;
+ }
}
void GinJavaBridgeDispatcherHost::RenderFrameCreated(
RenderFrameHost* render_frame_host) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ AddBrowserFilterIfNeeded();
for (NamedObjectMap::const_iterator iter = named_objects_.begin();
iter != named_objects_.end();
++iter) {
@@ -72,35 +109,40 @@ void GinJavaBridgeDispatcherHost::RenderFrameCreated(
void GinJavaBridgeDispatcherHost::RenderFrameDeleted(
RenderFrameHost* render_frame_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
- IPC::Message* reply_msg = TakePendingReply(render_frame_host);
- if (reply_msg != NULL) {
- base::ListValue result;
- result.Append(base::Value::CreateNullValue());
- IPC::WriteParam(reply_msg, result);
- IPC::WriteParam(reply_msg, kGinJavaBridgeRenderFrameDeleted);
- render_frame_host->Send(reply_msg);
+ AddBrowserFilterIfNeeded();
+ base::AutoLock locker(objects_lock_);
+ auto iter = objects_.begin();
+ while (iter != objects_.end()) {
+ JavaObjectWeakGlobalRef ref =
+ RemoveHolderAndAdvanceLocked(render_frame_host->GetRoutingID(), &iter);
+ if (!ref.is_empty()) {
+ RemoveFromRetainedObjectSetLocked(ref);
+ }
}
- RemoveHolder(render_frame_host,
- GinJavaBoundObject::ObjectMap::iterator(&objects_),
- objects_.size());
}
GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject(
const base::android::JavaRef<jobject>& object,
const base::android::JavaRef<jclass>& safe_annotation_clazz,
bool is_named,
- RenderFrameHost* holder) {
+ int32 holder) {
+ // Can be called on any thread. Calls come from the UI thread via
+ // AddNamedObject, and from the background thread, when injected Java
+ // object's method returns a Java object.
DCHECK(is_named || holder);
- GinJavaBoundObject::ObjectID object_id;
JNIEnv* env = base::android::AttachCurrentThread();
JavaObjectWeakGlobalRef ref(env, object.obj());
- if (is_named) {
- object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>(
- GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz)));
- } else {
- object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>(
- GinJavaBoundObject::CreateTransient(
- ref, safe_annotation_clazz, holder)));
+ scoped_refptr<GinJavaBoundObject> new_object =
+ is_named ? GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz)
+ : GinJavaBoundObject::CreateTransient(ref, safe_annotation_clazz,
+ holder);
+ // Note that we are abusing the fact that StaticAtomicSequenceNumber
+ // uses Atomic32 as a counter, so it is guaranteed that it will not
+ // overflow our int32 IDs. IDs start from 1.
+ GinJavaBoundObject::ObjectID object_id = g_next_object_id.GetNext() + 1;
+ {
+ base::AutoLock locker(objects_lock_);
+ objects_[object_id] = new_object;
}
#if DCHECK_IS_ON
{
@@ -112,6 +154,7 @@ GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject(
base::android::ScopedJavaLocalRef<jobject> retained_object_set =
retained_object_set_.get(env);
if (!retained_object_set.is_null()) {
+ base::AutoLock locker(objects_lock_);
JNI_Java_HashSet_add(env, retained_object_set, object);
}
return object_id;
@@ -120,13 +163,14 @@ GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject(
bool GinJavaBridgeDispatcherHost::FindObjectId(
const base::android::JavaRef<jobject>& object,
GinJavaBoundObject::ObjectID* object_id) {
+ // Can be called on any thread.
JNIEnv* env = base::android::AttachCurrentThread();
- for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd();
- it.Advance()) {
+ base::AutoLock locker(objects_lock_);
+ for (const auto& pair : objects_) {
if (env->IsSameObject(
object.obj(),
- it.GetCurrentValue()->get()->GetLocalRef(env).obj())) {
- *object_id = it.GetCurrentKey();
+ pair.second->GetLocalRef(env).obj())) {
+ *object_id = pair.first;
return true;
}
}
@@ -135,36 +179,40 @@ bool GinJavaBridgeDispatcherHost::FindObjectId(
JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef(
GinJavaBoundObject::ObjectID object_id) {
- scoped_refptr<GinJavaBoundObject>* result = objects_.Lookup(object_id);
- scoped_refptr<GinJavaBoundObject> object(result ? *result : NULL);
+ scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
if (object.get())
return object->GetWeakRef();
else
return JavaObjectWeakGlobalRef();
}
-void GinJavaBridgeDispatcherHost::RemoveHolder(
- RenderFrameHost* holder,
- const GinJavaBoundObject::ObjectMap::iterator& from,
- size_t count) {
- JNIEnv* env = base::android::AttachCurrentThread();
- base::android::ScopedJavaLocalRef<jobject> retained_object_set =
- retained_object_set_.get(env);
- size_t i = 0;
- for (GinJavaBoundObject::ObjectMap::iterator it(from);
- !it.IsAtEnd() && i < count;
- it.Advance(), ++i) {
- scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue());
- if (object->IsNamed())
- continue;
+JavaObjectWeakGlobalRef
+GinJavaBridgeDispatcherHost::RemoveHolderAndAdvanceLocked(
+ int32 holder,
+ ObjectMap::iterator* iter_ptr) {
+ objects_lock_.AssertAcquired();
+ JavaObjectWeakGlobalRef result;
+ scoped_refptr<GinJavaBoundObject> object((*iter_ptr)->second);
+ if (!object->IsNamed()) {
object->RemoveHolder(holder);
if (!object->HasHolders()) {
- if (!retained_object_set.is_null()) {
- JNI_Java_HashSet_remove(
- env, retained_object_set, object->GetLocalRef(env));
- }
- objects_.Remove(it.GetCurrentKey());
+ result = object->GetWeakRef();
+ objects_.erase((*iter_ptr)++);
}
+ } else {
+ ++(*iter_ptr);
+ }
+ return result;
+}
+
+void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked(
+ const JavaObjectWeakGlobalRef& ref) {
+ objects_lock_.AssertAcquired();
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> retained_object_set =
+ retained_object_set_.get(env);
+ if (!retained_object_set.is_null()) {
+ JNI_Java_HashSet_remove(env, retained_object_set, ref.get(env));
}
}
@@ -185,12 +233,14 @@ void GinJavaBridgeDispatcherHost::AddNamedObject(
RemoveNamedObject(iter->first);
}
if (existing_object) {
- (*objects_.Lookup(object_id))->AddName();
+ base::AutoLock locker(objects_lock_);
+ objects_[object_id]->AddName();
} else {
- object_id = AddObject(object, safe_annotation_clazz, true, NULL);
+ object_id = AddObject(object, safe_annotation_clazz, true, 0);
}
named_objects_[name] = object_id;
+ AddBrowserFilterIfNeeded();
web_contents()->SendToAllFrames(
new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE, name, object_id));
}
@@ -206,9 +256,11 @@ void GinJavaBridgeDispatcherHost::RemoveNamedObject(
// |name| is from |named_objects_| it'll be valid after the remove below.
const std::string copied_name(name);
- scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(iter->second));
+ {
+ base::AutoLock locker(objects_lock_);
+ objects_[iter->second]->RemoveName();
+ }
named_objects_.erase(iter);
- object->RemoveName();
// As the object isn't going to be removed from the JavaScript side until the
// next page reload, calls to it must still work, thus we should continue to
@@ -220,6 +272,14 @@ void GinJavaBridgeDispatcherHost::RemoveNamedObject(
}
void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) {
+ if (!JavaBridgeThread::CurrentlyOn()) {
+ g_background_thread.Get().message_loop()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection,
+ this, allow));
+ return;
+ }
allow_object_contents_inspection_ = allow;
}
@@ -228,228 +288,159 @@ void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
// Called when the window object has been cleared in the main frame.
// That means, all sub-frames have also been cleared, so only named
// objects survived.
+ AddBrowserFilterIfNeeded();
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jobject> retained_object_set =
retained_object_set_.get(env);
+ base::AutoLock locker(objects_lock_);
if (!retained_object_set.is_null()) {
JNI_Java_HashSet_clear(env, retained_object_set);
}
-
- // We also need to add back the named objects we have so far as they
- // should survive navigations.
- for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd();
- it.Advance()) {
- scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue());
- if (object->IsNamed()) {
+ auto iter = objects_.begin();
+ while (iter != objects_.end()) {
+ if (iter->second->IsNamed()) {
if (!retained_object_set.is_null()) {
JNI_Java_HashSet_add(
- env, retained_object_set, object->GetLocalRef(env));
+ env, retained_object_set, iter->second->GetLocalRef(env));
}
+ ++iter;
} else {
- objects_.Remove(it.GetCurrentKey());
+ objects_.erase(iter++);
}
}
}
-namespace {
-
-// TODO(mnaganov): Implement passing of a parameter into sync message handlers.
-class MessageForwarder : public IPC::Sender {
- public:
- MessageForwarder(GinJavaBridgeDispatcherHost* gjbdh,
- RenderFrameHost* render_frame_host)
- : gjbdh_(gjbdh), render_frame_host_(render_frame_host) {}
- void OnGetMethods(GinJavaBoundObject::ObjectID object_id,
- IPC::Message* reply_msg) {
- gjbdh_->OnGetMethods(render_frame_host_,
- object_id,
- reply_msg);
- }
- void OnHasMethod(GinJavaBoundObject::ObjectID object_id,
- const std::string& method_name,
- IPC::Message* reply_msg) {
- gjbdh_->OnHasMethod(render_frame_host_,
- object_id,
- method_name,
- reply_msg);
- }
- void OnInvokeMethod(GinJavaBoundObject::ObjectID object_id,
- const std::string& method_name,
- const base::ListValue& arguments,
- IPC::Message* reply_msg) {
- gjbdh_->OnInvokeMethod(render_frame_host_,
- object_id,
- method_name,
- arguments,
- reply_msg);
+base::TaskRunner* GinJavaBridgeDispatcherHost::OverrideTaskRunnerForMessage(
+ const IPC::Message& message) {
+ GinJavaBoundObject::ObjectID object_id = 0;
+ // TODO(mnaganov): It's very sad that we have a BrowserMessageFilter per
+ // WebView instance. We should redesign to have a filter per RPH.
+ // Check, if the object ID in the message is known to this host. If not,
+ // this is a message for some other host. As all our IPC messages from the
+ // renderer start with object ID, we just fetch it directly from the
+ // message, considering sync and async messages separately.
+ switch (message.type()) {
+ case GinJavaBridgeHostMsg_GetMethods::ID:
+ case GinJavaBridgeHostMsg_HasMethod::ID:
+ case GinJavaBridgeHostMsg_InvokeMethod::ID: {
+ DCHECK(message.is_sync());
+ PickleIterator message_reader =
+ IPC::SyncMessage::GetDataIterator(&message);
+ if (!IPC::ReadParam(&message, &message_reader, &object_id))
+ return NULL;
+ break;
+ }
+ case GinJavaBridgeHostMsg_ObjectWrapperDeleted::ID: {
+ DCHECK(!message.is_sync());
+ PickleIterator message_reader(message);
+ if (!IPC::ReadParam(&message, &message_reader, &object_id))
+ return NULL;
+ break;
+ }
+ default:
+ NOTREACHED();
+ return NULL;
}
- virtual bool Send(IPC::Message* msg) OVERRIDE {
- NOTREACHED();
- return false;
+ {
+ base::AutoLock locker(objects_lock_);
+ if (objects_.find(object_id) != objects_.end()) {
+ return g_background_thread.Get().message_loop()->task_runner().get();
+ }
}
- private:
- GinJavaBridgeDispatcherHost* gjbdh_;
- RenderFrameHost* render_frame_host_;
-};
-
+ return NULL;
}
bool GinJavaBridgeDispatcherHost::OnMessageReceived(
- const IPC::Message& message,
- RenderFrameHost* render_frame_host) {
- DCHECK(render_frame_host);
+ const IPC::Message& message) {
+ // We can get here As WebContentsObserver also has OnMessageReceived,
+ // or because we have not provided a task runner in
+ // OverrideTaskRunnerForMessage. In either case, just bail out.
+ if (!JavaBridgeThread::CurrentlyOn())
+ return false;
+ SetCurrentRoutingID(message.routing_id());
bool handled = true;
- MessageForwarder forwarder(this, render_frame_host);
- IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(GinJavaBridgeDispatcherHost, message,
- render_frame_host)
- IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_GetMethods,
- &forwarder,
- MessageForwarder::OnGetMethods)
- IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_HasMethod,
- &forwarder,
- MessageForwarder::OnHasMethod)
- IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_InvokeMethod,
- &forwarder,
- MessageForwarder::OnInvokeMethod)
+ IPC_BEGIN_MESSAGE_MAP(GinJavaBridgeDispatcherHost, message)
+ IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_GetMethods, OnGetMethods)
+ IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_HasMethod, OnHasMethod)
+ IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_InvokeMethod, OnInvokeMethod)
IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted,
OnObjectWrapperDeleted)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
+ SetCurrentRoutingID(MSG_ROUTING_NONE);
return handled;
}
-namespace {
-
-class IsValidRenderFrameHostHelper
- : public base::RefCounted<IsValidRenderFrameHostHelper> {
- public:
- explicit IsValidRenderFrameHostHelper(RenderFrameHost* rfh_to_match)
- : rfh_to_match_(rfh_to_match), rfh_found_(false) {}
-
- bool rfh_found() { return rfh_found_; }
-
- void OnFrame(RenderFrameHost* rfh) {
- if (rfh_to_match_ == rfh) rfh_found_ = true;
+void GinJavaBridgeDispatcherHost::OnDestruct() const {
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ delete this;
+ } else {
+ BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
}
+}
- private:
- friend class base::RefCounted<IsValidRenderFrameHostHelper>;
-
- ~IsValidRenderFrameHostHelper() {}
-
- RenderFrameHost* rfh_to_match_;
- bool rfh_found_;
-
- DISALLOW_COPY_AND_ASSIGN(IsValidRenderFrameHostHelper);
-};
+int GinJavaBridgeDispatcherHost::GetCurrentRoutingID() const {
+ DCHECK(JavaBridgeThread::CurrentlyOn());
+ return current_routing_id_;
+}
-} // namespace
+void GinJavaBridgeDispatcherHost::SetCurrentRoutingID(int32 routing_id) {
+ DCHECK(JavaBridgeThread::CurrentlyOn());
+ current_routing_id_ = routing_id;
+}
-bool GinJavaBridgeDispatcherHost::IsValidRenderFrameHost(
- RenderFrameHost* render_frame_host) {
- scoped_refptr<IsValidRenderFrameHostHelper> helper =
- new IsValidRenderFrameHostHelper(render_frame_host);
- web_contents()->ForEachFrame(
- base::Bind(&IsValidRenderFrameHostHelper::OnFrame, helper));
- return helper->rfh_found();
+scoped_refptr<GinJavaBoundObject> GinJavaBridgeDispatcherHost::FindObject(
+ GinJavaBoundObject::ObjectID object_id) {
+ // Can be called on any thread.
+ base::AutoLock locker(objects_lock_);
+ auto iter = objects_.find(object_id);
+ if (iter != objects_.end())
+ return iter->second;
+ return NULL;
}
void GinJavaBridgeDispatcherHost::OnGetMethods(
- RenderFrameHost* render_frame_host,
GinJavaBoundObject::ObjectID object_id,
- IPC::Message* reply_msg) {
- DCHECK_CURRENTLY_ON(BrowserThread::UI);
- DCHECK(render_frame_host);
- if (!allow_object_contents_inspection_) {
- IPC::WriteParam(reply_msg, std::set<std::string>());
- render_frame_host->Send(reply_msg);
+ std::set<std::string>* returned_method_names) {
+ DCHECK(JavaBridgeThread::CurrentlyOn());
+ if (!allow_object_contents_inspection_)
return;
- }
- scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
- if (!object.get()) {
+ scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
+ if (object.get()) {
+ *returned_method_names = object->GetMethodNames();
+ } else {
LOG(ERROR) << "WebView: Unknown object: " << object_id;
- IPC::WriteParam(reply_msg, std::set<std::string>());
- render_frame_host->Send(reply_msg);
- return;
}
- DCHECK(!HasPendingReply(render_frame_host));
- pending_replies_[render_frame_host] = reply_msg;
- base::PostTaskAndReplyWithResult(
- g_background_thread.Get().message_loop()->message_loop_proxy().get(),
- FROM_HERE,
- base::Bind(&GinJavaBoundObject::GetMethodNames, object),
- base::Bind(&GinJavaBridgeDispatcherHost::SendMethods,
- AsWeakPtr(),
- render_frame_host));
-}
-
-void GinJavaBridgeDispatcherHost::SendMethods(
- RenderFrameHost* render_frame_host,
- const std::set<std::string>& method_names) {
- IPC::Message* reply_msg = TakePendingReply(render_frame_host);
- if (!reply_msg) {
- return;
- }
- IPC::WriteParam(reply_msg, method_names);
- render_frame_host->Send(reply_msg);
}
void GinJavaBridgeDispatcherHost::OnHasMethod(
- RenderFrameHost* render_frame_host,
GinJavaBoundObject::ObjectID object_id,
const std::string& method_name,
- IPC::Message* reply_msg) {
- DCHECK_CURRENTLY_ON(BrowserThread::UI);
- DCHECK(render_frame_host);
- scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
- if (!object.get()) {
+ bool* result) {
+ DCHECK(JavaBridgeThread::CurrentlyOn());
+ scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
+ if (object.get()) {
+ *result = object->HasMethod(method_name);
+ } else {
LOG(ERROR) << "WebView: Unknown object: " << object_id;
- IPC::WriteParam(reply_msg, false);
- render_frame_host->Send(reply_msg);
- return;
- }
- DCHECK(!HasPendingReply(render_frame_host));
- pending_replies_[render_frame_host] = reply_msg;
- base::PostTaskAndReplyWithResult(
- g_background_thread.Get().message_loop()->message_loop_proxy().get(),
- FROM_HERE,
- base::Bind(&GinJavaBoundObject::HasMethod, object, method_name),
- base::Bind(&GinJavaBridgeDispatcherHost::SendHasMethodReply,
- AsWeakPtr(),
- render_frame_host));
-}
-
-void GinJavaBridgeDispatcherHost::SendHasMethodReply(
- RenderFrameHost* render_frame_host,
- bool result) {
- IPC::Message* reply_msg = TakePendingReply(render_frame_host);
- if (!reply_msg) {
- return;
}
- IPC::WriteParam(reply_msg, result);
- render_frame_host->Send(reply_msg);
}
void GinJavaBridgeDispatcherHost::OnInvokeMethod(
- RenderFrameHost* render_frame_host,
GinJavaBoundObject::ObjectID object_id,
const std::string& method_name,
const base::ListValue& arguments,
- IPC::Message* reply_msg) {
- DCHECK_CURRENTLY_ON(BrowserThread::UI);
- DCHECK(render_frame_host);
- scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
+ base::ListValue* wrapped_result,
+ content::GinJavaBridgeError* error_code) {
+ DCHECK(JavaBridgeThread::CurrentlyOn());
+ DCHECK(GetCurrentRoutingID() != MSG_ROUTING_NONE);
+ scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
if (!object.get()) {
LOG(ERROR) << "WebView: Unknown object: " << object_id;
- base::ListValue result;
- result.Append(base::Value::CreateNullValue());
- IPC::WriteParam(reply_msg, result);
- IPC::WriteParam(reply_msg, kGinJavaBridgeUnknownObjectId);
- render_frame_host->Send(reply_msg);
+ wrapped_result->Append(base::Value::CreateNullValue());
+ *error_code = kGinJavaBridgeUnknownObjectId;
return;
}
- DCHECK(!HasPendingReply(render_frame_host));
- pending_replies_[render_frame_host] = reply_msg;
scoped_refptr<GinJavaMethodInvocationHelper> result =
new GinJavaMethodInvocationHelper(
make_scoped_ptr(new GinJavaBoundObjectDelegate(object))
@@ -457,109 +448,44 @@ void GinJavaBridgeDispatcherHost::OnInvokeMethod(
method_name,
arguments);
result->Init(this);
- g_background_thread.Get()
- .message_loop()
- ->message_loop_proxy()
- ->PostTaskAndReply(
- FROM_HERE,
- base::Bind(&GinJavaMethodInvocationHelper::Invoke, result),
- base::Bind(
- &GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult,
- AsWeakPtr(),
- render_frame_host,
- result));
-}
-
-void GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult(
- RenderFrameHost* render_frame_host,
- scoped_refptr<GinJavaMethodInvocationHelper> result) {
+ result->Invoke();
+ *error_code = result->GetInvocationError();
if (result->HoldsPrimitiveResult()) {
- IPC::Message* reply_msg = TakePendingReply(render_frame_host);
- if (!reply_msg) {
- return;
- }
- IPC::WriteParam(reply_msg, result->GetPrimitiveResult());
- IPC::WriteParam(reply_msg, result->GetInvocationError());
- render_frame_host->Send(reply_msg);
- } else {
- ProcessMethodInvocationObjectResult(render_frame_host, result);
- }
-}
-
-void GinJavaBridgeDispatcherHost::ProcessMethodInvocationObjectResult(
- RenderFrameHost* render_frame_host,
- scoped_refptr<GinJavaMethodInvocationHelper> result) {
- DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
- if (!IsValidRenderFrameHost(render_frame_host)) {
- // In this case, we must've already sent the reply when the render frame
- // was destroyed.
- DCHECK(!HasPendingReply(render_frame_host));
- return;
- }
-
- base::ListValue wrapped_result;
- if (!result->GetObjectResult().is_null()) {
+ scoped_ptr<base::ListValue> result_copy(
+ result->GetPrimitiveResult().DeepCopy());
+ wrapped_result->Swap(result_copy.get());
+ } else if (!result->GetObjectResult().is_null()) {
GinJavaBoundObject::ObjectID returned_object_id;
if (FindObjectId(result->GetObjectResult(), &returned_object_id)) {
- (*objects_.Lookup(returned_object_id))->AddHolder(render_frame_host);
+ base::AutoLock locker(objects_lock_);
+ objects_[returned_object_id]->AddHolder(GetCurrentRoutingID());
} else {
returned_object_id = AddObject(result->GetObjectResult(),
result->GetSafeAnnotationClass(),
false,
- render_frame_host);
+ GetCurrentRoutingID());
}
- wrapped_result.Append(
+ wrapped_result->Append(
GinJavaBridgeValue::CreateObjectIDValue(
returned_object_id).release());
} else {
- wrapped_result.Append(base::Value::CreateNullValue());
+ wrapped_result->Append(base::Value::CreateNullValue());
}
- IPC::Message* reply_msg = TakePendingReply(render_frame_host);
- if (!reply_msg) {
- return;
- }
- IPC::WriteParam(reply_msg, wrapped_result);
- IPC::WriteParam(reply_msg, result->GetInvocationError());
- render_frame_host->Send(reply_msg);
}
void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
- RenderFrameHost* render_frame_host,
GinJavaBoundObject::ObjectID object_id) {
- DCHECK_CURRENTLY_ON(BrowserThread::UI);
- DCHECK(render_frame_host);
- if (objects_.Lookup(object_id)) {
- GinJavaBoundObject::ObjectMap::iterator iter(&objects_);
- while (!iter.IsAtEnd() && iter.GetCurrentKey() != object_id)
- iter.Advance();
- DCHECK(!iter.IsAtEnd());
- RemoveHolder(render_frame_host, iter, 1);
- }
-}
-
-IPC::Message* GinJavaBridgeDispatcherHost::TakePendingReply(
- RenderFrameHost* render_frame_host) {
- if (!IsValidRenderFrameHost(render_frame_host)) {
- DCHECK(!HasPendingReply(render_frame_host));
- return NULL;
- }
-
- PendingReplyMap::iterator it = pending_replies_.find(render_frame_host);
- // There may be no pending reply if we're called from RenderFrameDeleted and
- // we already sent the reply through the regular route.
- if (it == pending_replies_.end()) {
- return NULL;
+ DCHECK(JavaBridgeThread::CurrentlyOn());
+ DCHECK(GetCurrentRoutingID() != MSG_ROUTING_NONE);
+ base::AutoLock locker(objects_lock_);
+ auto iter = objects_.find(object_id);
+ if (iter == objects_.end())
+ return;
+ JavaObjectWeakGlobalRef ref =
+ RemoveHolderAndAdvanceLocked(GetCurrentRoutingID(), &iter);
+ if (!ref.is_empty()) {
+ RemoveFromRetainedObjectSetLocked(ref);
}
-
- IPC::Message* reply_msg = it->second;
- pending_replies_.erase(it);
- return reply_msg;
-}
-
-bool GinJavaBridgeDispatcherHost::HasPendingReply(
- RenderFrameHost* render_frame_host) const {
- return pending_replies_.find(render_frame_host) != pending_replies_.end();
}
} // namespace content
diff --git a/content/browser/android/java/gin_java_bridge_dispatcher_host.h b/content/browser/android/java/gin_java_bridge_dispatcher_host.h
index 48fcbb588b..70df5926b2 100644
--- a/content/browser/android/java/gin_java_bridge_dispatcher_host.h
+++ b/content/browser/android/java/gin_java_bridge_dispatcher_host.h
@@ -12,8 +12,10 @@
#include "base/android/scoped_java_ref.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
#include "content/browser/android/java/gin_java_bound_object.h"
#include "content/browser/android/java/gin_java_method_invocation_helper.h"
+#include "content/public/browser/browser_message_filter.h"
#include "content/public/browser/web_contents_observer.h"
namespace base {
@@ -31,14 +33,13 @@ namespace content {
// proxy object is created in the renderer. An instance of this class exists
// for each RenderFrameHost.
class GinJavaBridgeDispatcherHost
- : public base::SupportsWeakPtr<GinJavaBridgeDispatcherHost>,
- public WebContentsObserver,
+ : public WebContentsObserver,
+ public BrowserMessageFilter,
public GinJavaMethodInvocationHelper::DispatcherDelegate {
public:
GinJavaBridgeDispatcherHost(WebContents* web_contents,
jobject retained_object_set);
- virtual ~GinJavaBridgeDispatcherHost();
void AddNamedObject(
const std::string& name,
@@ -51,53 +52,65 @@ class GinJavaBridgeDispatcherHost
virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE;
virtual void RenderFrameDeleted(RenderFrameHost* render_frame_host) OVERRIDE;
virtual void DocumentAvailableInMainFrame() OVERRIDE;
- virtual bool OnMessageReceived(const IPC::Message& message,
- RenderFrameHost* render_frame_host) OVERRIDE;
+
+ // BrowserMessageFilter
+ using BrowserMessageFilter::Send;
+ virtual void OnDestruct() const OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual base::TaskRunner* OverrideTaskRunnerForMessage(
+ const IPC::Message& message) OVERRIDE;
// GinJavaMethodInvocationHelper::DispatcherDelegate
virtual JavaObjectWeakGlobalRef GetObjectWeakRef(
GinJavaBoundObject::ObjectID object_id) OVERRIDE;
- void OnGetMethods(RenderFrameHost* render_frame_host,
- GinJavaBoundObject::ObjectID object_id,
- IPC::Message* reply_msg);
- void OnHasMethod(RenderFrameHost* render_frame_host,
- GinJavaBoundObject::ObjectID object_id,
- const std::string& method_name,
- IPC::Message* reply_msg);
- void OnInvokeMethod(RenderFrameHost* render_frame_host,
- GinJavaBoundObject::ObjectID object_id,
- const std::string& method_name,
- const base::ListValue& arguments,
- IPC::Message* reply_msg);
-
private:
- void OnObjectWrapperDeleted(RenderFrameHost* render_frame_host,
- GinJavaBoundObject::ObjectID object_id);
-
- bool IsValidRenderFrameHost(RenderFrameHost* render_frame_host);
- void SendMethods(RenderFrameHost* render_frame_host,
- const std::set<std::string>& method_names);
- void SendHasMethodReply(RenderFrameHost* render_frame_host,
- bool result);
- void ProcessMethodInvocationResult(
- RenderFrameHost* render_frame_host,
- scoped_refptr<GinJavaMethodInvocationHelper> result);
- void ProcessMethodInvocationObjectResult(
- RenderFrameHost* render_frame_host,
- scoped_refptr<GinJavaMethodInvocationHelper> result);
+ friend class BrowserThread;
+ friend class base::DeleteHelper<GinJavaBridgeDispatcherHost>;
+
+ typedef std::map<GinJavaBoundObject::ObjectID,
+ scoped_refptr<GinJavaBoundObject>> ObjectMap;
+
+ virtual ~GinJavaBridgeDispatcherHost() OVERRIDE;
+
+ void AddBrowserFilterIfNeeded();
+
+ // Run on any thread.
GinJavaBoundObject::ObjectID AddObject(
const base::android::JavaRef<jobject>& object,
const base::android::JavaRef<jclass>& safe_annotation_clazz,
bool is_named,
- RenderFrameHost* holder);
+ int32 holder);
+ scoped_refptr<GinJavaBoundObject> FindObject(
+ GinJavaBoundObject::ObjectID object_id);
bool FindObjectId(const base::android::JavaRef<jobject>& object,
GinJavaBoundObject::ObjectID* object_id);
- void RemoveHolder(RenderFrameHost* holder,
- const GinJavaBoundObject::ObjectMap::iterator& from,
- size_t count);
- bool HasPendingReply(RenderFrameHost* render_frame_host) const;
- IPC::Message* TakePendingReply(RenderFrameHost* render_frame_host);
+ void RemoveFromRetainedObjectSetLocked(const JavaObjectWeakGlobalRef& ref);
+ JavaObjectWeakGlobalRef RemoveHolderAndAdvanceLocked(
+ int32 holder,
+ ObjectMap::iterator* iter_ptr);
+
+ // Run on the background thread.
+ void OnGetMethods(GinJavaBoundObject::ObjectID object_id,
+ std::set<std::string>* returned_method_names);
+ void OnHasMethod(GinJavaBoundObject::ObjectID object_id,
+ const std::string& method_name,
+ bool* result);
+ void OnInvokeMethod(GinJavaBoundObject::ObjectID object_id,
+ const std::string& method_name,
+ const base::ListValue& arguments,
+ base::ListValue* result,
+ content::GinJavaBridgeError* error_code);
+ void OnObjectWrapperDeleted(GinJavaBoundObject::ObjectID object_id);
+ int GetCurrentRoutingID() const;
+ void SetCurrentRoutingID(int routing_id);
+
+ bool browser_filter_added_;
+
+ typedef std::map<std::string, GinJavaBoundObject::ObjectID> NamedObjectMap;
+ NamedObjectMap named_objects_;
+
+ // The following objects are used on both threads, so locking must be used.
// Every time a GinJavaBoundObject backed by a real Java object is
// created/destroyed, we insert/remove a strong ref to that Java object into
@@ -106,17 +119,15 @@ class GinJavaBridgeDispatcherHost
// and defined in Java so that pushing refs into it does not create new GC
// roots that would prevent ContentViewCore from being garbage collected.
JavaObjectWeakGlobalRef retained_object_set_;
- bool allow_object_contents_inspection_;
- GinJavaBoundObject::ObjectMap objects_;
- typedef std::map<std::string, GinJavaBoundObject::ObjectID> NamedObjectMap;
- NamedObjectMap named_objects_;
+ // Note that retained_object_set_ does not need to be consistent
+ // with objects_.
+ ObjectMap objects_;
+ base::Lock objects_lock_;
- // Keep track of pending calls out to Java such that we can send a synchronous
- // reply to the renderer waiting on the response should the RenderFrame be
- // destroyed while the reply is pending.
- // Only used on the UI thread.
- typedef std::map<RenderFrameHost*, IPC::Message*> PendingReplyMap;
- PendingReplyMap pending_replies_;
+ // The following objects are only used on the background thread.
+ bool allow_object_contents_inspection_;
+ // The routing id of the RenderFrameHost whose request we are processing.
+ int32 current_routing_id_;
DISALLOW_COPY_AND_ASSIGN(GinJavaBridgeDispatcherHost);
};
diff --git a/content/browser/android/java/gin_java_method_invocation_helper.h b/content/browser/android/java/gin_java_method_invocation_helper.h
index 014e311319..f7bdd57747 100644
--- a/content/browser/android/java/gin_java_method_invocation_helper.h
+++ b/content/browser/android/java/gin_java_method_invocation_helper.h
@@ -20,13 +20,10 @@ namespace content {
class JavaMethod;
-// Instances of this class are created and initialized on the UI thread, do
-// their work on the background thread, and then again examined on the UI
-// thread. They don't work on both threads simultaneously, though.
+// Instances of this class are created and used on the background thread.
class CONTENT_EXPORT GinJavaMethodInvocationHelper
: public base::RefCountedThreadSafe<GinJavaMethodInvocationHelper> {
public:
- // DispatcherDelegate is used on the UI thread
class DispatcherDelegate {
public:
DispatcherDelegate() {}
@@ -38,7 +35,6 @@ class CONTENT_EXPORT GinJavaMethodInvocationHelper
DISALLOW_COPY_AND_ASSIGN(DispatcherDelegate);
};
- // ObjectDelegate is used in the background thread
class ObjectDelegate {
public:
ObjectDelegate() {}
@@ -61,10 +57,8 @@ class CONTENT_EXPORT GinJavaMethodInvocationHelper
const base::ListValue& arguments);
void Init(DispatcherDelegate* dispatcher);
- // Called on the background thread
void Invoke();
- // Called on the UI thread
bool HoldsPrimitiveResult();
const base::ListValue& GetPrimitiveResult();
const base::android::JavaRef<jobject>& GetObjectResult();
@@ -75,7 +69,6 @@ class CONTENT_EXPORT GinJavaMethodInvocationHelper
friend class base::RefCountedThreadSafe<GinJavaMethodInvocationHelper>;
~GinJavaMethodInvocationHelper();
- // Called on the UI thread
void BuildObjectRefsFromListValue(DispatcherDelegate* dispatcher,
const base::Value* list_value);
void BuildObjectRefsFromDictionaryValue(DispatcherDelegate* dispatcher,
@@ -84,7 +77,6 @@ class CONTENT_EXPORT GinJavaMethodInvocationHelper
bool AppendObjectRef(DispatcherDelegate* dispatcher,
const base::Value* raw_value);
- // Called on the background thread.
void InvokeMethod(jobject object,
jclass clazz,
const JavaType& return_type,
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 b19a6b2748..a379a5e7be 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,10 +4,15 @@
package org.chromium.content.browser;
+import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
+
import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.Assert;
+
import org.chromium.base.test.util.Feature;
import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
+import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_shell_apk.ContentShellActivity;
import java.lang.annotation.Annotation;
@@ -16,6 +21,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.ref.WeakReference;
+import java.util.concurrent.CountDownLatch;
/**
* Part of the test suite for the Java Bridge. Tests a number of features including ...
@@ -511,6 +517,47 @@ public class JavaBridgeBasicsTest extends JavaBridgeTestBase {
@SmallTest
@Feature({"AndroidWebView", "Android-JavaBridge"})
+ public void testBlockingUiThreadDoesNotBlockCallsFromJs() throws Throwable {
+ class TestObject {
+ private CountDownLatch mLatch;
+ public TestObject() {
+ mLatch = new CountDownLatch(1);
+ }
+ public boolean waitOnTheLatch() throws Exception {
+ return mLatch.await(scaleTimeout(10000),
+ java.util.concurrent.TimeUnit.MILLISECONDS);
+ }
+ public void unlockTheLatch() throws Exception {
+ mTestController.setStringValue("unlocked");
+ mLatch.countDown();
+ }
+ }
+ final TestObject testObject = new TestObject();
+ injectObjectAndReload(testObject, "testObject");
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // loadUrl is asynchronous, the JS code will start running on the renderer
+ // thread. As soon as we exit loadUrl, the browser UI thread will be stuck waiting
+ // on the latch. If blocking the browser thread blocks Java Bridge, then the call
+ // to "unlockTheLatch()" will be executed after the waiting timeout, thus the
+ // string value will not yet be updated by the injected object.
+ mTestController.setStringValue("locked");
+ getWebContents().getNavigationController().loadUrl(new LoadUrlParams(
+ "javascript:(function() { testObject.unlockTheLatch() })()"));
+ try {
+ assertTrue(testObject.waitOnTheLatch());
+ } catch (Exception e) {
+ android.util.Log.e("JavaBridgeBasicsTest", "Wait exception", e);
+ Assert.fail("Wait exception");
+ }
+ assertEquals("unlocked", mTestController.getStringValue());
+ }
+ });
+ }
+
+ @SmallTest
+ @Feature({"AndroidWebView", "Android-JavaBridge"})
public void testPublicInheritedMethod() throws Throwable {
class Base {
public void method(int x) { mTestController.setIntValue(x); }