b4128c36d20fdb24a303d8a739bc40f6870239d4
[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_resource_dispatcher_host_delegate_android.h"
38 #include "xwalk/runtime/browser/xwalk_browser_context.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 // FIXME(wang16): Remove following methods after deprecated fields
75 // are not supported any more.
76 void PrintManifestDeprecationWarning(std::string field) {
77   LOG(WARNING) << "\"" << field << "\" is deprecated for Crosswalk. "
78       << "Please follow "
79       << "https://www.crosswalk-project.org/#documentation/manifest.";
80 }
81
82 bool ManifestHasPath(const xwalk::application::Manifest& manifest,
83                      const std::string& path,
84                      const std::string& deprecated_path) {
85   if (manifest.HasPath(path))
86     return true;
87   if (manifest.HasPath(deprecated_path)) {
88     PrintManifestDeprecationWarning(deprecated_path);
89     return true;
90   }
91   return false;
92 }
93
94 bool ManifestGetString(const xwalk::application::Manifest& manifest,
95                        const std::string& path,
96                        const std::string& deprecated_path,
97                        std::string* out_value) {
98   if (manifest.GetString(path, out_value))
99     return true;
100   if (manifest.GetString(deprecated_path, out_value)) {
101     PrintManifestDeprecationWarning(deprecated_path);
102     return true;
103   }
104   return false;
105 }
106
107 }  // namespace
108
109 // static
110 XWalkContent* XWalkContent::FromID(int render_process_id,
111                                    int render_view_id) {
112   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
113   const content::RenderViewHost* rvh =
114       content::RenderViewHost::FromID(render_process_id, render_view_id);
115   if (!rvh) return NULL;
116   content::WebContents* web_contents =
117       content::WebContents::FromRenderViewHost(rvh);
118   if (!web_contents) return NULL;
119   return FromWebContents(web_contents);
120 }
121
122 // static
123 XWalkContent* XWalkContent::FromWebContents(
124     content::WebContents* web_contents) {
125   return XWalkContentUserData::GetContents(web_contents);
126 }
127
128 XWalkContent::XWalkContent(scoped_ptr<content::WebContents> web_contents)
129     : web_contents_(web_contents.Pass()) {
130 }
131
132 XWalkContent::~XWalkContent() {
133 }
134
135 void XWalkContent::SetJavaPeers(JNIEnv* env,
136                                 jobject obj,
137                                 jobject xwalk_content,
138                                 jobject web_contents_delegate,
139                                 jobject contents_client_bridge,
140                                 jobject io_thread_client,
141                                 jobject intercept_navigation_delegate) {
142   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
143   java_ref_ = JavaObjectWeakGlobalRef(env, xwalk_content);
144
145   web_contents_delegate_.reset(new XWalkWebContentsDelegate(
146       env, web_contents_delegate));
147   contents_client_bridge_.reset(new XWalkContentsClientBridge(
148       env, contents_client_bridge, web_contents_.get()));
149
150   web_contents_->SetUserData(
151       kXWalkContentUserDataKey, new XWalkContentUserData(this));
152
153   XWalkContentsIoThreadClientImpl::RegisterPendingContents(web_contents_.get());
154
155   // XWalk does not use disambiguation popup for multiple targets.
156   content::RendererPreferences* prefs =
157       web_contents_->GetMutableRendererPrefs();
158   prefs->tap_multiple_targets_strategy =
159       content::TAP_MULTIPLE_TARGETS_STRATEGY_NONE;
160
161   XWalkContentsClientBridgeBase::Associate(web_contents_.get(),
162       contents_client_bridge_.get());
163   XWalkContentsIoThreadClientImpl::Associate(web_contents_.get(),
164       ScopedJavaLocalRef<jobject>(env, io_thread_client));
165   int render_process_id = web_contents_->GetRenderProcessHost()->GetID();
166   int render_frame_id = web_contents_->GetRoutingID();
167   RuntimeResourceDispatcherHostDelegateAndroid::OnIoThreadClientReady(
168       render_process_id, render_frame_id);
169   InterceptNavigationDelegate::Associate(web_contents_.get(),
170       make_scoped_ptr(new InterceptNavigationDelegate(
171           env, intercept_navigation_delegate)));
172   web_contents_->SetDelegate(web_contents_delegate_.get());
173
174   render_view_host_ext_.reset(new XWalkRenderViewHostExt(web_contents_.get()));
175 }
176
177 jlong XWalkContent::GetWebContents(JNIEnv* env, jobject obj) {
178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179   DCHECK(web_contents_);
180   return reinterpret_cast<intptr_t>(web_contents_.get());
181 }
182
183 void XWalkContent::SetPendingWebContentsForPopup(
184     scoped_ptr<content::WebContents> pending) {
185   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
186   if (pending_contents_.get()) {
187     // TODO(benm): Support holding multiple pop up window requests.
188     LOG(WARNING) << "Blocking popup window creation as an outstanding "
189                  << "popup window is still pending.";
190     base::MessageLoop::current()->DeleteSoon(FROM_HERE, pending.release());
191     return;
192   }
193   pending_contents_.reset(new XWalkContent(pending.Pass()));
194 }
195
196 jlong XWalkContent::ReleasePopupXWalkContent(JNIEnv* env, jobject obj) {
197   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
198   return reinterpret_cast<intptr_t>(pending_contents_.release());
199 }
200
201 void XWalkContent::ClearCache(
202     JNIEnv* env,
203     jobject obj,
204     jboolean include_disk_files) {
205   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
206   render_view_host_ext_->ClearCache();
207
208   if (include_disk_files) {
209     RemoveHttpDiskCache(web_contents_->GetBrowserContext(),
210                         web_contents_->GetRoutingID());
211   }
212 }
213
214 ScopedJavaLocalRef<jstring> XWalkContent::DevToolsAgentId(JNIEnv* env,
215                                                           jobject obj) {
216   scoped_refptr<content::DevToolsAgentHost> agent_host(
217       content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get()));
218   return base::android::ConvertUTF8ToJavaString(env, agent_host->GetId());
219 }
220
221 void XWalkContent::Destroy(JNIEnv* env, jobject obj) {
222   delete this;
223 }
224
225 ScopedJavaLocalRef<jstring> XWalkContent::GetVersion(JNIEnv* env,
226                                                      jobject obj) {
227   return base::android::ConvertUTF8ToJavaString(env, XWALK_VERSION);
228 }
229
230 void XWalkContent::SetJsOnlineProperty(JNIEnv* env,
231                                        jobject obj,
232                                        jboolean network_up) {
233   render_view_host_ext_->SetJsOnlineProperty(network_up);
234 }
235
236 jboolean XWalkContent::SetManifest(JNIEnv* env,
237                                    jobject obj,
238                                    jstring path,
239                                    jstring manifest_string) {
240   std::string path_str = base::android::ConvertJavaStringToUTF8(env, path);
241   std::string json_input =
242       base::android::ConvertJavaStringToUTF8(env, manifest_string);
243
244   base::Value* manifest_value = base::JSONReader::Read(json_input);
245   if (!manifest_value) return false;
246
247   base::DictionaryValue* manifest_dictionary;
248   manifest_value->GetAsDictionary(&manifest_dictionary);
249   if (!manifest_dictionary) return false;
250
251   scoped_ptr<base::DictionaryValue>
252       manifest_dictionary_ptr(manifest_dictionary);
253
254   xwalk::application::Manifest manifest(
255       manifest_dictionary_ptr.Pass());
256
257   std::string url;
258   if (manifest.GetString(keys::kStartURLKey, &url)) {
259     std::string scheme = GURL(url).scheme();
260     if (scheme.empty())
261       url = path_str + url;
262   } else if (manifest.GetString(keys::kLaunchLocalPathKey, &url)) {
263     PrintManifestDeprecationWarning(keys::kLaunchLocalPathKey);
264     // According to original proposal for "app:launch:local_path", the "http"
265     // and "https" schemes are supported. So |url| should do nothing when it
266     // already has "http" or "https" scheme.
267     std::string scheme = GURL(url).scheme();
268     if (scheme != url::kHttpScheme && scheme != url::kHttpsScheme)
269       url = path_str + url;
270   } else if (manifest.GetString(keys::kLaunchWebURLKey, &url)) {
271     PrintManifestDeprecationWarning(keys::kLaunchWebURLKey);
272   } else {
273     NOTIMPLEMENTED();
274   }
275
276   std::string match_patterns;
277   const base::ListValue* xwalk_hosts = NULL;
278   if (manifest.GetList(
279           xwalk::application_manifest_keys::kXWalkHostsKey, &xwalk_hosts)) {
280       base::JSONWriter::Write(xwalk_hosts, &match_patterns);
281   }
282   render_view_host_ext_->SetOriginAccessWhitelist(url, match_patterns);
283
284   std::string csp;
285   ManifestGetString(manifest, keys::kCSPKey, keys::kDeprecatedCSPKey, &csp);
286   XWalkBrowserContext* browser_context =
287       XWalkRunner::GetInstance()->browser_context();
288   CHECK(browser_context);
289   browser_context->SetCSPString(csp);
290
291   ScopedJavaLocalRef<jstring> url_buffer =
292       base::android::ConvertUTF8ToJavaString(env, url);
293
294   if (manifest.HasPath(kDisplay)) {
295     std::string display_string;
296     if (manifest.GetString(kDisplay, &display_string)) {
297       // TODO(David): update the handling process of the display strings
298       // including fullscreen etc.
299       bool display_as_fullscreen =
300           LowerCaseEqualsASCII(display_string, "fullscreen");
301       Java_XWalkContent_onGetFullscreenFlagFromManifest(
302           env, obj, display_as_fullscreen ? JNI_TRUE : JNI_FALSE);
303     }
304   }
305
306   // Check whether need to display launch screen. (Read from manifest.json)
307   if (ManifestHasPath(manifest,
308                       keys::kXWalkLaunchScreen,
309                       keys::kLaunchScreen)) {
310     std::string ready_when;
311     // Get the value of 'ready_when' from manifest.json
312     ManifestGetString(manifest,
313                       keys::kXWalkLaunchScreenReadyWhen,
314                       keys::kLaunchScreenReadyWhen,
315                       &ready_when);
316     ScopedJavaLocalRef<jstring> ready_when_buffer =
317         base::android::ConvertUTF8ToJavaString(env, ready_when);
318
319     // Get the value of 'image_border'
320     // 1. When 'launch_screen.[orientation]' was defined, but no 'image_border'
321     //    The value of 'image_border' will be set as 'empty'.
322     // 2. Otherwise, there is no 'launch_screen.[orientation]' defined,
323     //    The value of 'image_border' will be empty.
324     const char empty[] = "empty";
325     std::string image_border_default;
326     ManifestGetString(manifest,
327                       keys::kXWalkLaunchScreenImageBorderDefault,
328                       keys::kLaunchScreenImageBorderDefault,
329                       &image_border_default);
330     if (image_border_default.empty() &&
331         ManifestHasPath(manifest,
332                         keys::kXWalkLaunchScreenDefault,
333                         keys::kLaunchScreenDefault)) {
334       image_border_default = empty;
335     }
336
337     std::string image_border_landscape;
338     ManifestGetString(manifest,
339                       keys::kXWalkLaunchScreenImageBorderLandscape,
340                       keys::kLaunchScreenImageBorderLandscape,
341                       &image_border_landscape);
342     if (image_border_landscape.empty() &&
343         ManifestHasPath(manifest,
344                         keys::kXWalkLaunchScreenLandscape,
345                         keys::kLaunchScreenLandscape)) {
346       image_border_landscape = empty;
347     }
348
349     std::string image_border_portrait;
350     ManifestGetString(manifest,
351                       keys::kXWalkLaunchScreenImageBorderPortrait,
352                       keys::kLaunchScreenImageBorderPortrait,
353                       &image_border_portrait);
354     if (image_border_portrait.empty() &&
355         ManifestHasPath(manifest,
356                         keys::kXWalkLaunchScreenPortrait,
357                         keys::kLaunchScreenPortrait)) {
358       image_border_portrait = empty;
359     }
360
361     std::string image_border = image_border_default + ';' +
362         image_border_landscape  + ';' + image_border_portrait;
363     ScopedJavaLocalRef<jstring> image_border_buffer =
364         base::android::ConvertUTF8ToJavaString(env, image_border);
365
366     Java_XWalkContent_onGetUrlAndLaunchScreenFromManifest(
367         env, obj, url_buffer.obj(), ready_when_buffer.obj(),
368         image_border_buffer.obj());
369   } else {
370     // No need to display launch screen, load the url directly.
371     Java_XWalkContent_onGetUrlFromManifest(env, obj, url_buffer.obj());
372   }
373   return true;
374 }
375
376 jint XWalkContent::GetRoutingID(JNIEnv* env, jobject obj) {
377   DCHECK(web_contents_.get());
378   return web_contents_->GetRoutingID();
379 }
380
381 base::android::ScopedJavaLocalRef<jbyteArray> XWalkContent::GetState(
382     JNIEnv* env,
383     jobject obj) {
384   if (!web_contents_->GetController().GetEntryCount())
385     return ScopedJavaLocalRef<jbyteArray>();
386
387   Pickle pickle;
388   if (!WriteToPickle(*web_contents_, &pickle)) {
389     return ScopedJavaLocalRef<jbyteArray>();
390   } else {
391     return base::android::ToJavaByteArray(
392         env,
393         reinterpret_cast<const uint8*>(pickle.data()),
394         pickle.size());
395   }
396 }
397
398 jboolean XWalkContent::SetState(JNIEnv* env, jobject obj, jbyteArray state) {
399   std::vector<uint8> state_vector;
400   base::android::JavaByteArrayToByteVector(env, state, &state_vector);
401
402   Pickle pickle(reinterpret_cast<const char*>(state_vector.begin()),
403                 state_vector.size());
404   PickleIterator iterator(pickle);
405
406   return RestoreFromPickle(&iterator, web_contents_.get());
407 }
408
409 static jlong Init(JNIEnv* env, jobject obj) {
410   scoped_ptr<WebContents> web_contents(content::WebContents::Create(
411       content::WebContents::CreateParams(
412           XWalkRunner::GetInstance()->browser_context())));
413   return reinterpret_cast<intptr_t>(new XWalkContent(web_contents.Pass()));
414 }
415
416 bool RegisterXWalkContent(JNIEnv* env) {
417   return RegisterNativesImpl(env);
418 }
419
420 namespace {
421
422 void ShowGeolocationPromptHelperTask(
423     const JavaObjectWeakGlobalRef& java_ref,
424     const GURL& origin) {
425   JNIEnv* env = AttachCurrentThread();
426   ScopedJavaLocalRef<jobject> j_ref = java_ref.get(env);
427   if (j_ref.obj()) {
428     ScopedJavaLocalRef<jstring> j_origin(
429         ConvertUTF8ToJavaString(env, origin.spec()));
430     Java_XWalkContent_onGeolocationPermissionsShowPrompt(env,
431                                                          j_ref.obj(),
432                                                          j_origin.obj());
433   }
434 }
435
436 void ShowGeolocationPromptHelper(const JavaObjectWeakGlobalRef& java_ref,
437                                  const GURL& origin) {
438   JNIEnv* env = AttachCurrentThread();
439   if (java_ref.get(env).obj()) {
440     content::BrowserThread::PostTask(
441         content::BrowserThread::UI,
442         FROM_HERE,
443         base::Bind(&ShowGeolocationPromptHelperTask,
444                    java_ref,
445                    origin));
446   }
447 }
448 }  // anonymous namespace
449
450 void XWalkContent::ShowGeolocationPrompt(
451     const GURL& requesting_frame,
452     const base::Callback<void(bool)>& callback) { // NOLINT
453   GURL origin = requesting_frame.GetOrigin();
454   bool show_prompt = pending_geolocation_prompts_.empty();
455   pending_geolocation_prompts_.push_back(OriginCallback(origin, callback));
456   if (show_prompt) {
457     ShowGeolocationPromptHelper(java_ref_, origin);
458   }
459 }
460
461 // Called by Java.
462 void XWalkContent::InvokeGeolocationCallback(JNIEnv* env,
463                                              jobject obj,
464                                              jboolean value,
465                                              jstring origin) {
466   GURL callback_origin(base::android::ConvertJavaStringToUTF16(env, origin));
467   if (callback_origin.GetOrigin() ==
468       pending_geolocation_prompts_.front().first) {
469     pending_geolocation_prompts_.front().second.Run(value);
470     pending_geolocation_prompts_.pop_front();
471     if (!pending_geolocation_prompts_.empty()) {
472       ShowGeolocationPromptHelper(java_ref_,
473                                   pending_geolocation_prompts_.front().first);
474     }
475   }
476 }
477
478 void XWalkContent::HideGeolocationPrompt(const GURL& origin) {
479   bool removed_current_outstanding_callback = false;
480   std::list<OriginCallback>::iterator it = pending_geolocation_prompts_.begin();
481   while (it != pending_geolocation_prompts_.end()) {
482     if ((*it).first == origin.GetOrigin()) {
483       if (it == pending_geolocation_prompts_.begin()) {
484         removed_current_outstanding_callback = true;
485       }
486       it = pending_geolocation_prompts_.erase(it);
487     } else {
488       ++it;
489     }
490   }
491
492   if (removed_current_outstanding_callback) {
493     JNIEnv* env = AttachCurrentThread();
494     ScopedJavaLocalRef<jobject> j_ref = java_ref_.get(env);
495     if (j_ref.obj()) {
496       Java_XWalkContent_onGeolocationPermissionsHidePrompt(env, j_ref.obj());
497     }
498     if (!pending_geolocation_prompts_.empty()) {
499       ShowGeolocationPromptHelper(java_ref_,
500                             pending_geolocation_prompts_.front().first);
501     }
502   }
503 }
504
505 // Called by Java.
506 void XWalkContent::SetBackgroundColor(JNIEnv* env, jobject obj, jint color) {
507   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
508   render_view_host_ext_->SetBackgroundColor(color);
509 }
510
511 }  // namespace xwalk