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.
6 #include "xwalk/runtime/browser/android/xwalk_content.h"
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"
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;
50 namespace keys = xwalk::application_manifest_keys;
56 const void* kXWalkContentUserDataKey = &kXWalkContentUserDataKey;
58 class XWalkContentUserData : public base::SupportsUserData::Data {
60 explicit XWalkContentUserData(XWalkContent* ptr) : content_(ptr) {}
62 static XWalkContent* GetContents(content::WebContents* web_contents) {
65 XWalkContentUserData* data = reinterpret_cast<XWalkContentUserData*>(
66 web_contents->GetUserData(kXWalkContentUserDataKey));
67 return data ? data->content_ : NULL;
71 XWalkContent* content_;
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. "
79 << "https://www.crosswalk-project.org/#documentation/manifest.";
82 bool ManifestHasPath(const xwalk::application::Manifest& manifest,
83 const std::string& path,
84 const std::string& deprecated_path) {
85 if (manifest.HasPath(path))
87 if (manifest.HasPath(deprecated_path)) {
88 PrintManifestDeprecationWarning(deprecated_path);
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))
100 if (manifest.GetString(deprecated_path, out_value)) {
101 PrintManifestDeprecationWarning(deprecated_path);
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);
123 XWalkContent* XWalkContent::FromWebContents(
124 content::WebContents* web_contents) {
125 return XWalkContentUserData::GetContents(web_contents);
128 XWalkContent::XWalkContent(scoped_ptr<content::WebContents> web_contents)
129 : web_contents_(web_contents.Pass()) {
132 XWalkContent::~XWalkContent() {
135 void XWalkContent::SetJavaPeers(JNIEnv* env,
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);
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()));
150 web_contents_->SetUserData(
151 kXWalkContentUserDataKey, new XWalkContentUserData(this));
153 XWalkContentsIoThreadClientImpl::RegisterPendingContents(web_contents_.get());
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;
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());
174 render_view_host_ext_.reset(new XWalkRenderViewHostExt(web_contents_.get()));
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());
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());
193 pending_contents_.reset(new XWalkContent(pending.Pass()));
196 jlong XWalkContent::ReleasePopupXWalkContent(JNIEnv* env, jobject obj) {
197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
198 return reinterpret_cast<intptr_t>(pending_contents_.release());
201 void XWalkContent::ClearCache(
204 jboolean include_disk_files) {
205 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
206 render_view_host_ext_->ClearCache();
208 if (include_disk_files) {
209 RemoveHttpDiskCache(web_contents_->GetBrowserContext(),
210 web_contents_->GetRoutingID());
214 ScopedJavaLocalRef<jstring> XWalkContent::DevToolsAgentId(JNIEnv* env,
216 scoped_refptr<content::DevToolsAgentHost> agent_host(
217 content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get()));
218 return base::android::ConvertUTF8ToJavaString(env, agent_host->GetId());
221 void XWalkContent::Destroy(JNIEnv* env, jobject obj) {
225 ScopedJavaLocalRef<jstring> XWalkContent::GetVersion(JNIEnv* env,
227 return base::android::ConvertUTF8ToJavaString(env, XWALK_VERSION);
230 void XWalkContent::SetJsOnlineProperty(JNIEnv* env,
232 jboolean network_up) {
233 render_view_host_ext_->SetJsOnlineProperty(network_up);
236 jboolean XWalkContent::SetManifest(JNIEnv* env,
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);
244 base::Value* manifest_value = base::JSONReader::Read(json_input);
245 if (!manifest_value) return false;
247 base::DictionaryValue* manifest_dictionary;
248 manifest_value->GetAsDictionary(&manifest_dictionary);
249 if (!manifest_dictionary) return false;
251 scoped_ptr<base::DictionaryValue>
252 manifest_dictionary_ptr(manifest_dictionary);
254 xwalk::application::Manifest manifest(
255 manifest_dictionary_ptr.Pass());
258 if (manifest.GetString(keys::kStartURLKey, &url)) {
259 std::string scheme = GURL(url).scheme();
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);
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);
282 render_view_host_ext_->SetOriginAccessWhitelist(url, match_patterns);
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);
291 ScopedJavaLocalRef<jstring> url_buffer =
292 base::android::ConvertUTF8ToJavaString(env, url);
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);
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,
316 ScopedJavaLocalRef<jstring> ready_when_buffer =
317 base::android::ConvertUTF8ToJavaString(env, ready_when);
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;
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;
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;
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);
366 Java_XWalkContent_onGetUrlAndLaunchScreenFromManifest(
367 env, obj, url_buffer.obj(), ready_when_buffer.obj(),
368 image_border_buffer.obj());
370 // No need to display launch screen, load the url directly.
371 Java_XWalkContent_onGetUrlFromManifest(env, obj, url_buffer.obj());
376 jint XWalkContent::GetRoutingID(JNIEnv* env, jobject obj) {
377 DCHECK(web_contents_.get());
378 return web_contents_->GetRoutingID();
381 base::android::ScopedJavaLocalRef<jbyteArray> XWalkContent::GetState(
384 if (!web_contents_->GetController().GetEntryCount())
385 return ScopedJavaLocalRef<jbyteArray>();
388 if (!WriteToPickle(*web_contents_, &pickle)) {
389 return ScopedJavaLocalRef<jbyteArray>();
391 return base::android::ToJavaByteArray(
393 reinterpret_cast<const uint8*>(pickle.data()),
398 jboolean XWalkContent::SetState(JNIEnv* env, jobject obj, jbyteArray state) {
399 std::vector<uint8> state_vector;
400 base::android::JavaByteArrayToByteVector(env, state, &state_vector);
402 Pickle pickle(reinterpret_cast<const char*>(state_vector.begin()),
403 state_vector.size());
404 PickleIterator iterator(pickle);
406 return RestoreFromPickle(&iterator, web_contents_.get());
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()));
416 bool RegisterXWalkContent(JNIEnv* env) {
417 return RegisterNativesImpl(env);
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);
428 ScopedJavaLocalRef<jstring> j_origin(
429 ConvertUTF8ToJavaString(env, origin.spec()));
430 Java_XWalkContent_onGeolocationPermissionsShowPrompt(env,
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,
443 base::Bind(&ShowGeolocationPromptHelperTask,
448 } // anonymous namespace
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));
457 ShowGeolocationPromptHelper(java_ref_, origin);
462 void XWalkContent::InvokeGeolocationCallback(JNIEnv* env,
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);
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;
486 it = pending_geolocation_prompts_.erase(it);
492 if (removed_current_outstanding_callback) {
493 JNIEnv* env = AttachCurrentThread();
494 ScopedJavaLocalRef<jobject> j_ref = java_ref_.get(env);
496 Java_XWalkContent_onGeolocationPermissionsHidePrompt(env, j_ref.obj());
498 if (!pending_geolocation_prompts_.empty()) {
499 ShowGeolocationPromptHelper(java_ref_,
500 pending_geolocation_prompts_.front().first);
506 void XWalkContent::SetBackgroundColor(JNIEnv* env, jobject obj, jint color) {
507 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
508 render_view_host_ext_->SetBackgroundColor(color);