1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h"
6
7#include "base/android/jni_android.h"
8#include "base/android/jni_helper.h"
9#include "base/android/scoped_java_ref.h"
10#include "base/bind.h"
11#include "base/logging.h"
12#include "base/strings/utf_string_conversions.h"
13#include "content/browser/renderer_host/java/java_bound_object.h"
14#include "content/browser/renderer_host/java/java_bridge_dispatcher_host.h"
15#include "content/common/android/hash_set.h"
16#include "content/public/browser/browser_thread.h"
17#include "third_party/WebKit/public/web/WebBindings.h"
18
19namespace content {
20
21JavaBridgeDispatcherHostManager::JavaBridgeDispatcherHostManager(
22    WebContents* web_contents)
23    : WebContentsObserver(web_contents) {
24}
25
26JavaBridgeDispatcherHostManager::~JavaBridgeDispatcherHostManager() {
27  for (ObjectMap::iterator iter = objects_.begin(); iter != objects_.end();
28      ++iter) {
29    WebKit::WebBindings::releaseObject(iter->second);
30  }
31  DCHECK_EQ(0U, instances_.size());
32}
33
34void JavaBridgeDispatcherHostManager::AddNamedObject(const string16& name,
35                                                     NPObject* object) {
36  // Record this object in a map so that we can add it into RenderViewHosts
37  // created later. The JavaBridgeDispatcherHost instances will take a
38  // reference to the object, but we take one too, because this method can be
39  // called before there are any such instances.
40  WebKit::WebBindings::retainObject(object);
41  objects_[name] = object;
42
43  for (InstanceMap::iterator iter = instances_.begin();
44      iter != instances_.end(); ++iter) {
45    iter->second->AddNamedObject(name, object);
46  }
47}
48
49void JavaBridgeDispatcherHostManager::SetRetainedObjectSet(
50    const JavaObjectWeakGlobalRef& retained_object_set) {
51  // It's an error to replace the retained_object_set_ after it's been set,
52  // so we check that it hasn't already been here.
53  // TODO(benm): It'd be better to pass the set in the constructor to avoid
54  // the chance of this happening; but that's tricky as this get's constructed
55  // before ContentViewCore (which owns the set). Best solution may be to move
56  // ownership of the JavaBridgerDispatchHostManager from WebContents to
57  // ContentViewCore?
58  JNIEnv* env = base::android::AttachCurrentThread();
59  base::android::ScopedJavaLocalRef<jobject> new_retained_object_set =
60      retained_object_set.get(env);
61  base::android::ScopedJavaLocalRef<jobject> current_retained_object_set =
62      retained_object_set_.get(env);
63  if (!env->IsSameObject(new_retained_object_set.obj(),
64                         current_retained_object_set.obj())) {
65    DCHECK(current_retained_object_set.is_null());
66    retained_object_set_ = retained_object_set;
67  }
68}
69
70void JavaBridgeDispatcherHostManager::RemoveNamedObject(const string16& name) {
71  ObjectMap::iterator iter = objects_.find(name);
72  if (iter == objects_.end()) {
73    return;
74  }
75
76  WebKit::WebBindings::releaseObject(iter->second);
77  objects_.erase(iter);
78
79  for (InstanceMap::iterator iter = instances_.begin();
80      iter != instances_.end(); ++iter) {
81    iter->second->RemoveNamedObject(name);
82  }
83}
84
85void JavaBridgeDispatcherHostManager::RenderViewCreated(
86    RenderViewHost* render_view_host) {
87  // Creates a JavaBridgeDispatcherHost for the specified RenderViewHost and
88  // adds all currently registered named objects to the new instance.
89  scoped_refptr<JavaBridgeDispatcherHost> instance =
90      new JavaBridgeDispatcherHost(render_view_host);
91
92  for (ObjectMap::const_iterator iter = objects_.begin();
93      iter != objects_.end(); ++iter) {
94    instance->AddNamedObject(iter->first, iter->second);
95  }
96
97  instances_[render_view_host] = instance;
98}
99
100void JavaBridgeDispatcherHostManager::RenderViewDeleted(
101    RenderViewHost* render_view_host) {
102  instances_.erase(render_view_host);
103}
104
105void JavaBridgeDispatcherHostManager::WebContentsDestroyed(
106    WebContents* web_contents) {
107  // When a WebContents is shutting down, it clears its observers before
108  // it kills all of its RenderViewHosts, so we won't get a call to
109  // RenderViewDeleted() for all RenderViewHosts.
110  instances_.clear();
111}
112
113void JavaBridgeDispatcherHostManager::DocumentAvailableInMainFrame() {
114  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
115  // Called when the window object has been cleared in the main frame.
116  JNIEnv* env = base::android::AttachCurrentThread();
117  base::android::ScopedJavaLocalRef<jobject> retained_object_set =
118      retained_object_set_.get(env);
119  if (!retained_object_set.is_null()) {
120    JNI_Java_HashSet_clear(env, retained_object_set);
121
122    // We also need to add back the named objects we have so far as they
123    // should survive navigations.
124    ObjectMap::iterator it = objects_.begin();
125    for (; it != objects_.end(); ++it) {
126      JNI_Java_HashSet_add(env, retained_object_set,
127                           JavaBoundObject::GetJavaObject(it->second));
128    }
129  }
130}
131
132void JavaBridgeDispatcherHostManager::JavaBoundObjectCreated(
133    const base::android::JavaRef<jobject>& object) {
134  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
135
136  JNIEnv* env = base::android::AttachCurrentThread();
137  base::android::ScopedJavaLocalRef<jobject> retained_object_set =
138      retained_object_set_.get(env);
139  if (!retained_object_set.is_null()) {
140    JNI_Java_HashSet_add(env, retained_object_set, object);
141  }
142}
143
144void JavaBridgeDispatcherHostManager::JavaBoundObjectDestroyed(
145    const base::android::JavaRef<jobject>& object) {
146  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
147
148  JNIEnv* env = base::android::AttachCurrentThread();
149  base::android::ScopedJavaLocalRef<jobject> retained_object_set =
150      retained_object_set_.get(env);
151  if (!retained_object_set.is_null()) {
152    JNI_Java_HashSet_remove(env, retained_object_set, object);
153  }
154}
155
156}  // namespace content
157