805a51b15faee8385f3f904bc5846135bf9acc81
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / browser / android / xwalk_content.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Copyright (c) 2013 Intel Corporation. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 #include "xwalk/runtime/browser/android/xwalk_content.h"
7
8 #include <algorithm>
9 #include <cctype>
10 #include <string>
11 #include <vector>
12
13 #include "base/android/jni_array.h"
14 #include "base/android/jni_string.h"
15 #include "base/base_paths_android.h"
16 #include "base/json/json_reader.h"
17 #include "base/json/json_writer.h"
18 #include "base/path_service.h"
19 #include "base/pickle.h"
20 #include "content/public/browser/browser_context.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/devtools_agent_host.h"
23 #include "content/public/browser/render_process_host.h"
24 #include "content/public/browser/render_view_host.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/common/renderer_preferences.h"
27 #include "content/public/common/url_constants.h"
28 #include "components/navigation_interception/intercept_navigation_delegate.h"
29 #include "xwalk/application/common/application_manifest_constants.h"
30 #include "xwalk/application/common/manifest.h"
31 #include "xwalk/runtime/browser/android/net_disk_cache_remover.h"
32 #include "xwalk/runtime/browser/android/state_serializer.h"
33 #include "xwalk/runtime/browser/android/xwalk_contents_client_bridge.h"
34 #include "xwalk/runtime/browser/android/xwalk_contents_client_bridge_base.h"
35 #include "xwalk/runtime/browser/android/xwalk_contents_io_thread_client_impl.h"
36 #include "xwalk/runtime/browser/android/xwalk_web_contents_delegate.h"
37 #include "xwalk/runtime/browser/runtime_context.h"
38 #include "xwalk/runtime/browser/runtime_resource_dispatcher_host_delegate_android.h"
39 #include "xwalk/runtime/browser/xwalk_runner.h"
40 #include "jni/XWalkContent_jni.h"
41
42 using base::android::AttachCurrentThread;
43 using base::android::ConvertUTF8ToJavaString;
44 using base::android::ScopedJavaLocalRef;
45 using content::BrowserThread;
46 using content::WebContents;
47 using navigation_interception::InterceptNavigationDelegate;
48 using xwalk::application_manifest_keys::kDisplay;
49
50 namespace keys = xwalk::application_manifest_keys;
51
52 namespace xwalk {
53
54 namespace {
55
56 const void* kXWalkContentUserDataKey = &kXWalkContentUserDataKey;
57
58 class XWalkContentUserData : public base::SupportsUserData::Data {
59  public:
60   explicit XWalkContentUserData(XWalkContent* ptr) : content_(ptr) {}
61
62   static XWalkContent* GetContents(content::WebContents* web_contents) {
63     if (!web_contents)
64       return NULL;
65     XWalkContentUserData* data = reinterpret_cast<XWalkContentUserData*>(
66         web_contents->GetUserData(kXWalkContentUserDataKey));
67     return data ? data->content_ : NULL;
68   }
69
70  private:
71   XWalkContent* content_;
72 };
73
74 }  // namespace
75
76 XWalkContent::XWalkContent(JNIEnv* env,
77                            jobject obj,
78                            jobject web_contents_delegate,
79                            jobject contents_client_bridge)
80     : java_ref_(env, obj),
81       web_contents_delegate_(
82           new XWalkWebContentsDelegate(env, web_contents_delegate)),
83       contents_client_bridge_(
84           new XWalkContentsClientBridge(env, contents_client_bridge)) {
85 }
86
87 XWalkContent::~XWalkContent() {
88 }
89
90 // static
91 XWalkContent* XWalkContent::FromID(int render_process_id,
92                                    int render_view_id) {
93   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
94   const content::RenderViewHost* rvh =
95       content::RenderViewHost::FromID(render_process_id, render_view_id);
96   if (!rvh) return NULL;
97   content::WebContents* web_contents =
98       content::WebContents::FromRenderViewHost(rvh);
99   if (!web_contents) return NULL;
100   return FromWebContents(web_contents);
101 }
102
103 // static
104 XWalkContent* XWalkContent::FromWebContents(
105     content::WebContents* web_contents) {
106   return XWalkContentUserData::GetContents(web_contents);
107 }
108
109 jlong XWalkContent::GetWebContents(
110     JNIEnv* env, jobject obj, jobject io_thread_client,
111     jobject intercept_navigation_delegate) {
112   if (!web_contents_) {
113     web_contents_.reset(CreateWebContents(env, io_thread_client,
114                                           intercept_navigation_delegate));
115
116     render_view_host_ext_.reset(
117         new XWalkRenderViewHostExt(web_contents_.get()));
118   }
119   return reinterpret_cast<intptr_t>(web_contents_.get());
120 }
121
122 content::WebContents* XWalkContent::CreateWebContents(
123     JNIEnv* env, jobject io_thread_client,
124     jobject intercept_navigation_delegate) {
125
126   RuntimeContext* runtime_context =
127       XWalkRunner::GetInstance()->runtime_context();
128   CHECK(runtime_context);
129
130   content::WebContents* web_contents = content::WebContents::Create(
131       content::WebContents::CreateParams(runtime_context));
132
133   web_contents->SetUserData(kXWalkContentUserDataKey,
134                             new XWalkContentUserData(this));
135
136   XWalkContentsIoThreadClientImpl::RegisterPendingContents(web_contents);
137
138   // XWalk does not use disambiguation popup for multiple targets.
139   content::RendererPreferences* prefs =
140       web_contents->GetMutableRendererPrefs();
141   prefs->tap_multiple_targets_strategy =
142       content::TAP_MULTIPLE_TARGETS_STRATEGY_NONE;
143
144   XWalkContentsClientBridgeBase::Associate(web_contents,
145       contents_client_bridge_.get());
146   XWalkContentsIoThreadClientImpl::Associate(web_contents,
147       ScopedJavaLocalRef<jobject>(env, io_thread_client));
148   int child_id = web_contents->GetRenderProcessHost()->GetID();
149   int route_id = web_contents->GetRoutingID();
150   RuntimeResourceDispatcherHostDelegateAndroid::OnIoThreadClientReady(
151       child_id, route_id);
152   InterceptNavigationDelegate::Associate(web_contents,
153       make_scoped_ptr(new InterceptNavigationDelegate(
154           env, intercept_navigation_delegate)));
155   web_contents->SetDelegate(web_contents_delegate_.get());
156   return web_contents;
157 }
158
159 void XWalkContent::ClearCache(
160     JNIEnv* env,
161     jobject obj,
162     jboolean include_disk_files) {
163   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
164   render_view_host_ext_->ClearCache();
165
166   if (include_disk_files) {
167     RemoveHttpDiskCache(web_contents_->GetBrowserContext(),
168                         web_contents_->GetRoutingID());
169   }
170 }
171
172 ScopedJavaLocalRef<jstring> XWalkContent::DevToolsAgentId(JNIEnv* env,
173                                                           jobject obj) {
174   content::RenderViewHost* rvh = web_contents_->GetRenderViewHost();
175   scoped_refptr<content::DevToolsAgentHost> agent_host(
176       content::DevToolsAgentHost::GetOrCreateFor(rvh));
177   return base::android::ConvertUTF8ToJavaString(env, agent_host->GetId());
178 }
179
180 void XWalkContent::Destroy(JNIEnv* env, jobject obj) {
181   delete this;
182 }
183
184 ScopedJavaLocalRef<jstring> XWalkContent::GetVersion(JNIEnv* env,
185                                                      jobject obj) {
186   return base::android::ConvertUTF8ToJavaString(env, XWALK_VERSION);
187 }
188
189 void XWalkContent::SetJsOnlineProperty(JNIEnv* env,
190                                        jobject obj,
191                                        jboolean network_up) {
192   render_view_host_ext_->SetJsOnlineProperty(network_up);
193 }
194
195 jboolean XWalkContent::SetManifest(JNIEnv* env,
196                                    jobject obj,
197                                    jstring path,
198                                    jstring manifest_string) {
199   std::string path_str = base::android::ConvertJavaStringToUTF8(env, path);
200   std::string json_input =
201       base::android::ConvertJavaStringToUTF8(env, manifest_string);
202
203   base::Value* manifest_value = base::JSONReader::Read(json_input);
204   if (!manifest_value) return false;
205
206   base::DictionaryValue* manifest_dictionary;
207   manifest_value->GetAsDictionary(&manifest_dictionary);
208   if (!manifest_dictionary) return false;
209
210   scoped_ptr<base::DictionaryValue>
211       manifest_dictionary_ptr(manifest_dictionary);
212
213   xwalk::application::Manifest manifest(
214       xwalk::application::Manifest::INVALID_TYPE,
215       manifest_dictionary_ptr.Pass());
216
217   std::string url;
218   if (manifest.GetString(keys::kLaunchLocalPathKey, &url)) {
219     // According to original proposal for "app:launch:local_path", the "http"
220     // and "https" schemes are supported. So |url| should do nothing when it
221     // already has "http" or "https" scheme.
222     std::string scheme = GURL(url).scheme();
223     if (scheme != url::kHttpScheme && scheme != url::kHttpsScheme)
224       url = path_str + url;
225   } else {
226     manifest.GetString(keys::kLaunchWebURLKey, &url);
227   }
228
229   std::string match_patterns;
230   const base::ListValue* xwalk_hosts = NULL;
231   if (manifest.GetList(
232           xwalk::application_manifest_keys::kXWalkHostsKey, &xwalk_hosts)) {
233       base::JSONWriter::Write(xwalk_hosts, &match_patterns);
234   }
235   render_view_host_ext_->SetOriginAccessWhitelist(url, match_patterns);
236
237   std::string csp;
238   manifest.GetString(keys::kCSPKey, &csp);
239   RuntimeContext* runtime_context =
240       XWalkRunner::GetInstance()->runtime_context();
241   CHECK(runtime_context);
242   runtime_context->SetCSPString(csp);
243
244   ScopedJavaLocalRef<jstring> url_buffer =
245       base::android::ConvertUTF8ToJavaString(env, url);
246
247   if (manifest.HasPath(kDisplay)) {
248     std::string display_string;
249     manifest.GetString(kDisplay, &display_string);
250     // TODO(David): update the handling process of the display strings
251     // including fullscreen etc.
252     bool display_as_fullscreen = (
253         display_string.find("fullscreen") != std::string::npos);
254     Java_XWalkContent_onGetFullscreenFlagFromManifest(
255         env, obj, display_as_fullscreen ? JNI_TRUE : JNI_FALSE);
256   }
257
258   // Check whether need to display launch screen. (Read from manifest.json)
259   if (manifest.HasPath(keys::kLaunchScreen)) {
260     std::string ready_when;
261     // Get the value of 'ready_when' from manifest.json
262     manifest.GetString(keys::kLaunchScreenReadyWhen, &ready_when);
263     ScopedJavaLocalRef<jstring> ready_when_buffer =
264         base::android::ConvertUTF8ToJavaString(env, ready_when);
265
266     // Get the value of 'image_border'
267     // 1. When 'launch_screen.[orientation]' was defined, but no 'image_border'
268     //    The value of 'image_border' will be set as 'empty'.
269     // 2. Otherwise, there is no 'launch_screen.[orientation]' defined,
270     //    The value of 'image_border' will be empty.
271     const char empty[] = "empty";
272     std::string image_border_default;
273     manifest.GetString(keys::kLaunchScreenImageBorderDefault,
274                        &image_border_default);
275     if (image_border_default.empty() && manifest.HasPath(
276         keys::kLaunchScreenDefault)) {
277       image_border_default = empty;
278     }
279
280     std::string image_border_landscape;
281     manifest.GetString(keys::kLaunchScreenImageBorderLandscape,
282                        &image_border_landscape);
283     if (image_border_landscape.empty() && manifest.HasPath(
284         keys::kLaunchScreenLandscape)) {
285       image_border_landscape = empty;
286     }
287
288     std::string image_border_portrait;
289     manifest.GetString(keys::kLaunchScreenImageBorderPortrait,
290                        &image_border_portrait);
291     if (image_border_portrait.empty() && manifest.HasPath(
292         keys::kLaunchScreenPortrait)) {
293       image_border_portrait = empty;
294     }
295
296     std::string image_border = image_border_default + ';' +
297         image_border_landscape  + ';' + image_border_portrait;
298     ScopedJavaLocalRef<jstring> image_border_buffer =
299         base::android::ConvertUTF8ToJavaString(env, image_border);
300
301     Java_XWalkContent_onGetUrlAndLaunchScreenFromManifest(
302         env, obj, url_buffer.obj(), ready_when_buffer.obj(),
303         image_border_buffer.obj());
304   } else {
305     // No need to display launch screen, load the url directly.
306     Java_XWalkContent_onGetUrlFromManifest(env, obj, url_buffer.obj());
307   }
308   return true;
309 }
310
311 jint XWalkContent::GetRoutingID(JNIEnv* env, jobject obj) {
312   DCHECK(web_contents_.get());
313   return web_contents_->GetRoutingID();
314 }
315
316 base::android::ScopedJavaLocalRef<jbyteArray> XWalkContent::GetState(
317     JNIEnv* env,
318     jobject obj) {
319   if (!web_contents_->GetController().GetEntryCount())
320     return ScopedJavaLocalRef<jbyteArray>();
321
322   Pickle pickle;
323   if (!WriteToPickle(*web_contents_, &pickle)) {
324     return ScopedJavaLocalRef<jbyteArray>();
325   } else {
326     return base::android::ToJavaByteArray(
327         env,
328         reinterpret_cast<const uint8*>(pickle.data()),
329         pickle.size());
330   }
331 }
332
333 jboolean XWalkContent::SetState(JNIEnv* env, jobject obj, jbyteArray state) {
334   std::vector<uint8> state_vector;
335   base::android::JavaByteArrayToByteVector(env, state, &state_vector);
336
337   Pickle pickle(reinterpret_cast<const char*>(state_vector.begin()),
338                 state_vector.size());
339   PickleIterator iterator(pickle);
340
341   return RestoreFromPickle(&iterator, web_contents_.get());
342 }
343
344 static jlong Init(JNIEnv* env, jobject obj, jobject web_contents_delegate,
345     jobject contents_client_bridge) {
346   XWalkContent* xwalk_core_content =
347     new XWalkContent(env, obj, web_contents_delegate, contents_client_bridge);
348   return reinterpret_cast<intptr_t>(xwalk_core_content);
349 }
350
351 bool RegisterXWalkContent(JNIEnv* env) {
352   return RegisterNativesImpl(env) >= 0;
353 }
354
355 namespace {
356
357 void ShowGeolocationPromptHelperTask(
358     const JavaObjectWeakGlobalRef& java_ref,
359     const GURL& origin) {
360   JNIEnv* env = AttachCurrentThread();
361   ScopedJavaLocalRef<jobject> j_ref = java_ref.get(env);
362   if (j_ref.obj()) {
363     ScopedJavaLocalRef<jstring> j_origin(
364         ConvertUTF8ToJavaString(env, origin.spec()));
365     Java_XWalkContent_onGeolocationPermissionsShowPrompt(env,
366                                                          j_ref.obj(),
367                                                          j_origin.obj());
368   }
369 }
370
371 void ShowGeolocationPromptHelper(const JavaObjectWeakGlobalRef& java_ref,
372                                  const GURL& origin) {
373   JNIEnv* env = AttachCurrentThread();
374   if (java_ref.get(env).obj()) {
375     content::BrowserThread::PostTask(
376         content::BrowserThread::UI,
377         FROM_HERE,
378         base::Bind(&ShowGeolocationPromptHelperTask,
379                    java_ref,
380                    origin));
381   }
382 }
383 }  // anonymous namespace
384
385 void XWalkContent::ShowGeolocationPrompt(
386     const GURL& requesting_frame,
387     const base::Callback<void(bool)>& callback) { // NOLINT
388   GURL origin = requesting_frame.GetOrigin();
389   bool show_prompt = pending_geolocation_prompts_.empty();
390   pending_geolocation_prompts_.push_back(OriginCallback(origin, callback));
391   if (show_prompt) {
392     ShowGeolocationPromptHelper(java_ref_, origin);
393   }
394 }
395
396 // Called by Java.
397 void XWalkContent::InvokeGeolocationCallback(JNIEnv* env,
398                                              jobject obj,
399                                              jboolean value,
400                                              jstring origin) {
401   GURL callback_origin(base::android::ConvertJavaStringToUTF16(env, origin));
402   if (callback_origin.GetOrigin() ==
403       pending_geolocation_prompts_.front().first) {
404     pending_geolocation_prompts_.front().second.Run(value);
405     pending_geolocation_prompts_.pop_front();
406     if (!pending_geolocation_prompts_.empty()) {
407       ShowGeolocationPromptHelper(java_ref_,
408                                   pending_geolocation_prompts_.front().first);
409     }
410   }
411 }
412
413 void XWalkContent::HideGeolocationPrompt(const GURL& origin) {
414   bool removed_current_outstanding_callback = false;
415   std::list<OriginCallback>::iterator it = pending_geolocation_prompts_.begin();
416   while (it != pending_geolocation_prompts_.end()) {
417     if ((*it).first == origin.GetOrigin()) {
418       if (it == pending_geolocation_prompts_.begin()) {
419         removed_current_outstanding_callback = true;
420       }
421       it = pending_geolocation_prompts_.erase(it);
422     } else {
423       ++it;
424     }
425   }
426
427   if (removed_current_outstanding_callback) {
428     JNIEnv* env = AttachCurrentThread();
429     ScopedJavaLocalRef<jobject> j_ref = java_ref_.get(env);
430     if (j_ref.obj()) {
431       Java_XWalkContent_onGeolocationPermissionsHidePrompt(env, j_ref.obj());
432     }
433     if (!pending_geolocation_prompts_.empty()) {
434       ShowGeolocationPromptHelper(java_ref_,
435                             pending_geolocation_prompts_.front().first);
436     }
437   }
438 }
439 }  // namespace xwalk