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 "android_webview/native/aw_contents_io_thread_client_impl.h"
6
7#include <map>
8#include <utility>
9
10#include "android_webview/common/devtools_instrumentation.h"
11#include "android_webview/native/aw_web_resource_response_impl.h"
12#include "base/android/jni_array.h"
13#include "base/android/jni_string.h"
14#include "base/android/jni_weak_ref.h"
15#include "base/lazy_instance.h"
16#include "base/memory/linked_ptr.h"
17#include "base/memory/scoped_ptr.h"
18#include "base/synchronization/lock.h"
19#include "content/public/browser/browser_thread.h"
20#include "content/public/browser/render_frame_host.h"
21#include "content/public/browser/render_process_host.h"
22#include "content/public/browser/render_view_host.h"
23#include "content/public/browser/resource_request_info.h"
24#include "content/public/browser/web_contents.h"
25#include "content/public/browser/web_contents_observer.h"
26#include "jni/AwContentsIoThreadClient_jni.h"
27#include "net/http/http_request_headers.h"
28#include "net/url_request/url_request.h"
29#include "url/gurl.h"
30
31using base::android::AttachCurrentThread;
32using base::android::ConvertUTF8ToJavaString;
33using base::android::JavaRef;
34using base::android::ScopedJavaLocalRef;
35using base::android::ToJavaArrayOfStrings;
36using base::LazyInstance;
37using content::BrowserThread;
38using content::RenderFrameHost;
39using content::ResourceType;
40using content::WebContents;
41using std::map;
42using std::pair;
43using std::string;
44using std::vector;
45
46namespace android_webview {
47
48namespace {
49
50struct IoThreadClientData {
51  bool pending_association;
52  JavaObjectWeakGlobalRef io_thread_client;
53
54  IoThreadClientData();
55};
56
57IoThreadClientData::IoThreadClientData() : pending_association(false) {}
58
59typedef map<pair<int, int>, IoThreadClientData>
60    RenderFrameHostToIoThreadClientType;
61
62static pair<int, int> GetRenderFrameHostIdPair(RenderFrameHost* rfh) {
63  return pair<int, int>(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
64}
65
66// RfhToIoThreadClientMap -----------------------------------------------------
67class RfhToIoThreadClientMap {
68 public:
69  static RfhToIoThreadClientMap* GetInstance();
70  void Set(pair<int, int> rfh_id, const IoThreadClientData& client);
71  bool Get(pair<int, int> rfh_id, IoThreadClientData* client);
72  void Erase(pair<int, int> rfh_id);
73
74 private:
75  static LazyInstance<RfhToIoThreadClientMap> g_instance_;
76  base::Lock map_lock_;
77  RenderFrameHostToIoThreadClientType rfh_to_io_thread_client_;
78};
79
80// static
81LazyInstance<RfhToIoThreadClientMap> RfhToIoThreadClientMap::g_instance_ =
82    LAZY_INSTANCE_INITIALIZER;
83
84// static
85RfhToIoThreadClientMap* RfhToIoThreadClientMap::GetInstance() {
86  return g_instance_.Pointer();
87}
88
89void RfhToIoThreadClientMap::Set(pair<int, int> rfh_id,
90                                 const IoThreadClientData& client) {
91  base::AutoLock lock(map_lock_);
92  rfh_to_io_thread_client_[rfh_id] = client;
93}
94
95bool RfhToIoThreadClientMap::Get(
96    pair<int, int> rfh_id, IoThreadClientData* client) {
97  base::AutoLock lock(map_lock_);
98  RenderFrameHostToIoThreadClientType::iterator iterator =
99      rfh_to_io_thread_client_.find(rfh_id);
100  if (iterator == rfh_to_io_thread_client_.end())
101    return false;
102
103  *client = iterator->second;
104  return true;
105}
106
107void RfhToIoThreadClientMap::Erase(pair<int, int> rfh_id) {
108  base::AutoLock lock(map_lock_);
109  rfh_to_io_thread_client_.erase(rfh_id);
110}
111
112// ClientMapEntryUpdater ------------------------------------------------------
113
114class ClientMapEntryUpdater : public content::WebContentsObserver {
115 public:
116  ClientMapEntryUpdater(JNIEnv* env, WebContents* web_contents,
117                        jobject jdelegate);
118
119  virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE;
120  virtual void RenderFrameDeleted(RenderFrameHost* render_frame_host) OVERRIDE;
121  virtual void WebContentsDestroyed() OVERRIDE;
122
123 private:
124  JavaObjectWeakGlobalRef jdelegate_;
125};
126
127ClientMapEntryUpdater::ClientMapEntryUpdater(JNIEnv* env,
128                                             WebContents* web_contents,
129                                             jobject jdelegate)
130    : content::WebContentsObserver(web_contents),
131      jdelegate_(env, jdelegate) {
132  DCHECK(web_contents);
133  DCHECK(jdelegate);
134
135  if (web_contents->GetMainFrame())
136    RenderFrameCreated(web_contents->GetMainFrame());
137}
138
139void ClientMapEntryUpdater::RenderFrameCreated(RenderFrameHost* rfh) {
140  IoThreadClientData client_data;
141  client_data.io_thread_client = jdelegate_;
142  client_data.pending_association = false;
143  RfhToIoThreadClientMap::GetInstance()->Set(
144      GetRenderFrameHostIdPair(rfh), client_data);
145}
146
147void ClientMapEntryUpdater::RenderFrameDeleted(RenderFrameHost* rfh) {
148  RfhToIoThreadClientMap::GetInstance()->Erase(GetRenderFrameHostIdPair(rfh));
149}
150
151void ClientMapEntryUpdater::WebContentsDestroyed() {
152  delete this;
153}
154
155} // namespace
156
157// AwContentsIoThreadClientImpl -----------------------------------------------
158
159// static
160scoped_ptr<AwContentsIoThreadClient>
161AwContentsIoThreadClient::FromID(int render_process_id, int render_frame_id) {
162  pair<int, int> rfh_id(render_process_id, render_frame_id);
163  IoThreadClientData client_data;
164  if (!RfhToIoThreadClientMap::GetInstance()->Get(rfh_id, &client_data))
165    return scoped_ptr<AwContentsIoThreadClient>();
166
167  JNIEnv* env = AttachCurrentThread();
168  ScopedJavaLocalRef<jobject> java_delegate =
169      client_data.io_thread_client.get(env);
170  DCHECK(!client_data.pending_association || java_delegate.is_null());
171  return scoped_ptr<AwContentsIoThreadClient>(new AwContentsIoThreadClientImpl(
172      client_data.pending_association, java_delegate));
173}
174
175// static
176void AwContentsIoThreadClient::SubFrameCreated(int render_process_id,
177                                               int parent_render_frame_id,
178                                               int child_render_frame_id) {
179  pair<int, int> parent_rfh_id(render_process_id, parent_render_frame_id);
180  pair<int, int> child_rfh_id(render_process_id, child_render_frame_id);
181  IoThreadClientData client_data;
182  if (!RfhToIoThreadClientMap::GetInstance()->Get(parent_rfh_id,
183                                                  &client_data)) {
184    NOTREACHED();
185    return;
186  }
187
188  RfhToIoThreadClientMap::GetInstance()->Set(child_rfh_id, client_data);
189}
190
191// static
192void AwContentsIoThreadClientImpl::RegisterPendingContents(
193    WebContents* web_contents) {
194  IoThreadClientData client_data;
195  client_data.pending_association = true;
196  RfhToIoThreadClientMap::GetInstance()->Set(
197      GetRenderFrameHostIdPair(web_contents->GetMainFrame()), client_data);
198}
199
200// static
201void AwContentsIoThreadClientImpl::Associate(
202    WebContents* web_contents,
203    const JavaRef<jobject>& jclient) {
204  JNIEnv* env = AttachCurrentThread();
205  // The ClientMapEntryUpdater lifespan is tied to the WebContents.
206  new ClientMapEntryUpdater(env, web_contents, jclient.obj());
207}
208
209AwContentsIoThreadClientImpl::AwContentsIoThreadClientImpl(
210    bool pending_association,
211    const JavaRef<jobject>& obj)
212  : pending_association_(pending_association),
213    java_object_(obj) {
214}
215
216AwContentsIoThreadClientImpl::~AwContentsIoThreadClientImpl() {
217  // explict, out-of-line destructor.
218}
219
220bool AwContentsIoThreadClientImpl::PendingAssociation() const {
221  return pending_association_;
222}
223
224AwContentsIoThreadClient::CacheMode
225AwContentsIoThreadClientImpl::GetCacheMode() const {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
227  if (java_object_.is_null())
228    return AwContentsIoThreadClient::LOAD_DEFAULT;
229
230  JNIEnv* env = AttachCurrentThread();
231  return static_cast<AwContentsIoThreadClient::CacheMode>(
232      Java_AwContentsIoThreadClient_getCacheMode(
233          env, java_object_.obj()));
234}
235
236scoped_ptr<AwWebResourceResponse>
237AwContentsIoThreadClientImpl::ShouldInterceptRequest(
238    const GURL& location,
239    const net::URLRequest* request) {
240  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
241  if (java_object_.is_null())
242    return scoped_ptr<AwWebResourceResponse>();
243  const content::ResourceRequestInfo* info =
244      content::ResourceRequestInfo::ForRequest(request);
245  bool is_main_frame = info &&
246      info->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME;
247  bool has_user_gesture = info && info->HasUserGesture();
248
249  vector<string> headers_names;
250  vector<string> headers_values;
251  {
252    net::HttpRequestHeaders headers;
253    if (!request->GetFullRequestHeaders(&headers))
254      headers = request->extra_request_headers();
255    net::HttpRequestHeaders::Iterator headers_iterator(headers);
256    while (headers_iterator.GetNext()) {
257      headers_names.push_back(headers_iterator.name());
258      headers_values.push_back(headers_iterator.value());
259    }
260  }
261
262  JNIEnv* env = AttachCurrentThread();
263  ScopedJavaLocalRef<jstring> jstring_url =
264      ConvertUTF8ToJavaString(env, location.spec());
265  ScopedJavaLocalRef<jstring> jstring_method =
266      ConvertUTF8ToJavaString(env, request->method());
267  ScopedJavaLocalRef<jobjectArray> jstringArray_headers_names =
268      ToJavaArrayOfStrings(env, headers_names);
269  ScopedJavaLocalRef<jobjectArray> jstringArray_headers_values =
270      ToJavaArrayOfStrings(env, headers_values);
271  devtools_instrumentation::ScopedEmbedderCallbackTask embedder_callback(
272      "shouldInterceptRequest");
273  ScopedJavaLocalRef<jobject> ret =
274      Java_AwContentsIoThreadClient_shouldInterceptRequest(
275          env,
276          java_object_.obj(),
277          jstring_url.obj(),
278          is_main_frame,
279          has_user_gesture,
280          jstring_method.obj(),
281          jstringArray_headers_names.obj(),
282          jstringArray_headers_values.obj());
283  if (ret.is_null())
284    return scoped_ptr<AwWebResourceResponse>();
285  return scoped_ptr<AwWebResourceResponse>(
286      new AwWebResourceResponseImpl(ret));
287}
288
289bool AwContentsIoThreadClientImpl::ShouldBlockContentUrls() const {
290  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
291  if (java_object_.is_null())
292    return false;
293
294  JNIEnv* env = AttachCurrentThread();
295  return Java_AwContentsIoThreadClient_shouldBlockContentUrls(
296      env, java_object_.obj());
297}
298
299bool AwContentsIoThreadClientImpl::ShouldBlockFileUrls() const {
300  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
301  if (java_object_.is_null())
302    return false;
303
304  JNIEnv* env = AttachCurrentThread();
305  return Java_AwContentsIoThreadClient_shouldBlockFileUrls(
306      env, java_object_.obj());
307}
308
309bool AwContentsIoThreadClientImpl::ShouldAcceptThirdPartyCookies() const {
310  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
311  if (java_object_.is_null())
312    return false;
313
314  JNIEnv* env = AttachCurrentThread();
315  return Java_AwContentsIoThreadClient_shouldAcceptThirdPartyCookies(
316      env, java_object_.obj());
317}
318
319bool AwContentsIoThreadClientImpl::ShouldBlockNetworkLoads() const {
320  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
321  if (java_object_.is_null())
322    return false;
323
324  JNIEnv* env = AttachCurrentThread();
325  return Java_AwContentsIoThreadClient_shouldBlockNetworkLoads(
326      env, java_object_.obj());
327}
328
329void AwContentsIoThreadClientImpl::NewDownload(
330    const GURL& url,
331    const string& user_agent,
332    const string& content_disposition,
333    const string& mime_type,
334    int64 content_length) {
335  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
336  if (java_object_.is_null())
337    return;
338
339  JNIEnv* env = AttachCurrentThread();
340  ScopedJavaLocalRef<jstring> jstring_url =
341      ConvertUTF8ToJavaString(env, url.spec());
342  ScopedJavaLocalRef<jstring> jstring_user_agent =
343      ConvertUTF8ToJavaString(env, user_agent);
344  ScopedJavaLocalRef<jstring> jstring_content_disposition =
345      ConvertUTF8ToJavaString(env, content_disposition);
346  ScopedJavaLocalRef<jstring> jstring_mime_type =
347      ConvertUTF8ToJavaString(env, mime_type);
348
349  Java_AwContentsIoThreadClient_onDownloadStart(
350      env,
351      java_object_.obj(),
352      jstring_url.obj(),
353      jstring_user_agent.obj(),
354      jstring_content_disposition.obj(),
355      jstring_mime_type.obj(),
356      content_length);
357}
358
359void AwContentsIoThreadClientImpl::NewLoginRequest(const string& realm,
360                                                   const string& account,
361                                                   const string& args) {
362  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
363  if (java_object_.is_null())
364    return;
365
366  JNIEnv* env = AttachCurrentThread();
367  ScopedJavaLocalRef<jstring> jrealm = ConvertUTF8ToJavaString(env, realm);
368  ScopedJavaLocalRef<jstring> jargs = ConvertUTF8ToJavaString(env, args);
369
370  ScopedJavaLocalRef<jstring> jaccount;
371  if (!account.empty())
372    jaccount = ConvertUTF8ToJavaString(env, account);
373
374  Java_AwContentsIoThreadClient_newLoginRequest(
375      env, java_object_.obj(), jrealm.obj(), jaccount.obj(), jargs.obj());
376}
377
378bool RegisterAwContentsIoThreadClientImpl(JNIEnv* env) {
379  return RegisterNativesImpl(env);
380}
381
382} // namespace android_webview
383