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 "components/web_contents_delegate_android/web_contents_delegate_android.h"
6
7#include <android/keycodes.h>
8
9#include "base/android/jni_android.h"
10#include "base/android/jni_array.h"
11#include "base/android/jni_string.h"
12#include "components/web_contents_delegate_android/color_chooser_android.h"
13#include "components/web_contents_delegate_android/validation_message_bubble_android.h"
14#include "content/public/browser/android/content_view_core.h"
15#include "content/public/browser/color_chooser.h"
16#include "content/public/browser/global_request_id.h"
17#include "content/public/browser/invalidate_type.h"
18#include "content/public/browser/native_web_keyboard_event.h"
19#include "content/public/browser/navigation_controller.h"
20#include "content/public/browser/page_navigator.h"
21#include "content/public/browser/render_widget_host_view.h"
22#include "content/public/browser/web_contents.h"
23#include "content/public/common/referrer.h"
24#include "jni/WebContentsDelegateAndroid_jni.h"
25#include "ui/base/window_open_disposition.h"
26#include "ui/gfx/rect.h"
27#include "url/gurl.h"
28
29using base::android::AttachCurrentThread;
30using base::android::ConvertUTF8ToJavaString;
31using base::android::ConvertUTF16ToJavaString;
32using base::android::ScopedJavaLocalRef;
33using content::ColorChooser;
34using content::RenderWidgetHostView;
35using content::WebContents;
36using content::WebContentsDelegate;
37
38namespace web_contents_delegate_android {
39
40WebContentsDelegateAndroid::WebContentsDelegateAndroid(JNIEnv* env, jobject obj)
41    : weak_java_delegate_(env, obj) {
42}
43
44WebContentsDelegateAndroid::~WebContentsDelegateAndroid() {
45}
46
47ScopedJavaLocalRef<jobject>
48WebContentsDelegateAndroid::GetJavaDelegate(JNIEnv* env) const {
49  return weak_java_delegate_.get(env);
50}
51
52// ----------------------------------------------------------------------------
53// WebContentsDelegate methods
54// ----------------------------------------------------------------------------
55
56ColorChooser* WebContentsDelegateAndroid::OpenColorChooser(
57      WebContents* source,
58      SkColor color,
59      const std::vector<content::ColorSuggestion>& suggestions)  {
60  return new ColorChooserAndroid(source, color, suggestions);
61}
62
63// OpenURLFromTab() will be called when we're performing a browser-intiated
64// navigation. The most common scenario for this is opening new tabs (see
65// RenderViewImpl::decidePolicyForNavigation for more details).
66WebContents* WebContentsDelegateAndroid::OpenURLFromTab(
67    WebContents* source,
68    const content::OpenURLParams& params) {
69  const GURL& url = params.url;
70  WindowOpenDisposition disposition = params.disposition;
71
72  if (!source || (disposition != CURRENT_TAB &&
73                  disposition != NEW_FOREGROUND_TAB &&
74                  disposition != NEW_BACKGROUND_TAB &&
75                  disposition != OFF_THE_RECORD)) {
76    NOTIMPLEMENTED();
77    return NULL;
78  }
79
80  JNIEnv* env = AttachCurrentThread();
81  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
82  if (obj.is_null())
83    return WebContentsDelegate::OpenURLFromTab(source, params);
84
85  if (disposition == NEW_FOREGROUND_TAB ||
86      disposition == NEW_BACKGROUND_TAB ||
87      disposition == OFF_THE_RECORD) {
88    JNIEnv* env = AttachCurrentThread();
89    ScopedJavaLocalRef<jstring> java_url =
90        ConvertUTF8ToJavaString(env, url.spec());
91    ScopedJavaLocalRef<jstring> extra_headers =
92            ConvertUTF8ToJavaString(env, params.extra_headers);
93    ScopedJavaLocalRef<jbyteArray> post_data;
94    if (params.uses_post &&
95        params.browser_initiated_post_data.get() &&
96        params.browser_initiated_post_data.get()->size()) {
97      post_data = base::android::ToJavaByteArray(
98          env,
99          params.browser_initiated_post_data.get()->front_as<uint8>(),
100          params.browser_initiated_post_data.get()->size());
101    }
102    Java_WebContentsDelegateAndroid_openNewTab(env,
103                                               obj.obj(),
104                                               java_url.obj(),
105                                               extra_headers.obj(),
106                                               post_data.obj(),
107                                               disposition,
108                                               params.is_renderer_initiated);
109    return NULL;
110  }
111
112  // content::OpenURLParams -> content::NavigationController::LoadURLParams
113  content::NavigationController::LoadURLParams load_params(url);
114  load_params.referrer = params.referrer;
115  load_params.frame_tree_node_id = params.frame_tree_node_id;
116  load_params.redirect_chain = params.redirect_chain;
117  load_params.transition_type = params.transition;
118  load_params.extra_headers = params.extra_headers;
119  load_params.should_replace_current_entry =
120      params.should_replace_current_entry;
121  load_params.is_renderer_initiated = params.is_renderer_initiated;
122
123  if (params.transferred_global_request_id != content::GlobalRequestID()) {
124    load_params.transferred_global_request_id =
125        params.transferred_global_request_id;
126  }
127
128  // Only allows the browser-initiated navigation to use POST.
129  if (params.uses_post && !params.is_renderer_initiated) {
130    load_params.load_type =
131        content::NavigationController::LOAD_TYPE_BROWSER_INITIATED_HTTP_POST;
132    load_params.browser_initiated_post_data =
133        params.browser_initiated_post_data;
134  }
135
136  source->GetController().LoadURLWithParams(load_params);
137
138  return source;
139}
140
141void WebContentsDelegateAndroid::NavigationStateChanged(
142    const WebContents* source, content::InvalidateTypes changed_flags) {
143  JNIEnv* env = AttachCurrentThread();
144  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
145  if (obj.is_null())
146    return;
147  Java_WebContentsDelegateAndroid_navigationStateChanged(
148      env,
149      obj.obj(),
150      changed_flags);
151}
152
153void WebContentsDelegateAndroid::VisibleSSLStateChanged(
154    const WebContents* source) {
155  JNIEnv* env = AttachCurrentThread();
156  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
157  if (obj.is_null())
158    return;
159  Java_WebContentsDelegateAndroid_visibleSSLStateChanged(
160      env,
161      obj.obj());
162}
163
164void WebContentsDelegateAndroid::ActivateContents(WebContents* contents) {
165  JNIEnv* env = AttachCurrentThread();
166  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
167  if (obj.is_null())
168    return;
169  Java_WebContentsDelegateAndroid_activateContents(env, obj.obj());
170}
171
172void WebContentsDelegateAndroid::DeactivateContents(WebContents* contents) {
173  // On desktop the current window is deactivated here, bringing the next window
174  // to focus. Not implemented on Android.
175}
176
177void WebContentsDelegateAndroid::LoadingStateChanged(WebContents* source,
178    bool to_different_document) {
179  JNIEnv* env = AttachCurrentThread();
180  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
181  if (obj.is_null())
182    return;
183  bool has_stopped = source == NULL || !source->IsLoading();
184
185  if (has_stopped)
186    Java_WebContentsDelegateAndroid_onLoadStopped(env, obj.obj());
187  else
188    Java_WebContentsDelegateAndroid_onLoadStarted(env, obj.obj());
189}
190
191void WebContentsDelegateAndroid::LoadProgressChanged(WebContents* source,
192                                                     double progress) {
193  JNIEnv* env = AttachCurrentThread();
194  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
195  if (obj.is_null())
196    return;
197  Java_WebContentsDelegateAndroid_notifyLoadProgressChanged(
198      env,
199      obj.obj(),
200      progress);
201}
202
203void WebContentsDelegateAndroid::RendererUnresponsive(WebContents* source) {
204  JNIEnv* env = AttachCurrentThread();
205  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
206  if (obj.is_null())
207    return;
208  Java_WebContentsDelegateAndroid_rendererUnresponsive(env, obj.obj());
209}
210
211void WebContentsDelegateAndroid::RendererResponsive(WebContents* source) {
212  JNIEnv* env = AttachCurrentThread();
213  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
214  if (obj.is_null())
215    return;
216  Java_WebContentsDelegateAndroid_rendererResponsive(env, obj.obj());
217}
218
219void WebContentsDelegateAndroid::CloseContents(WebContents* source) {
220  JNIEnv* env = AttachCurrentThread();
221  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
222  if (obj.is_null())
223    return;
224  Java_WebContentsDelegateAndroid_closeContents(env, obj.obj());
225}
226
227void WebContentsDelegateAndroid::MoveContents(WebContents* source,
228                                              const gfx::Rect& pos) {
229  // Do nothing.
230}
231
232bool WebContentsDelegateAndroid::AddMessageToConsole(
233    WebContents* source,
234    int32 level,
235    const base::string16& message,
236    int32 line_no,
237    const base::string16& source_id) {
238  JNIEnv* env = AttachCurrentThread();
239  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
240  if (obj.is_null())
241    return WebContentsDelegate::AddMessageToConsole(source, level, message,
242                                                    line_no, source_id);
243  ScopedJavaLocalRef<jstring> jmessage(ConvertUTF16ToJavaString(env, message));
244  ScopedJavaLocalRef<jstring> jsource_id(
245      ConvertUTF16ToJavaString(env, source_id));
246  int jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_DEBUG;
247  switch (level) {
248    case logging::LOG_VERBOSE:
249      jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_DEBUG;
250      break;
251    case logging::LOG_INFO:
252      jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_LOG;
253      break;
254    case logging::LOG_WARNING:
255      jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_WARNING;
256      break;
257    case logging::LOG_ERROR:
258      jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_ERROR;
259      break;
260    default:
261      NOTREACHED();
262  }
263  return Java_WebContentsDelegateAndroid_addMessageToConsole(
264      env,
265      GetJavaDelegate(env).obj(),
266      jlevel,
267      jmessage.obj(),
268      line_no,
269      jsource_id.obj());
270}
271
272// This is either called from TabContents::DidNavigateMainFramePostCommit() with
273// an empty GURL or responding to RenderViewHost::OnMsgUpateTargetURL(). In
274// Chrome, the latter is not always called, especially not during history
275// navigation. So we only handle the first case and pass the source TabContents'
276// url to Java to update the UI.
277void WebContentsDelegateAndroid::UpdateTargetURL(WebContents* source,
278                                                 const GURL& url) {
279  if (!url.is_empty())
280    return;
281  JNIEnv* env = AttachCurrentThread();
282  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
283  if (obj.is_null())
284    return;
285  ScopedJavaLocalRef<jstring> java_url =
286      ConvertUTF8ToJavaString(env, source->GetURL().spec());
287  Java_WebContentsDelegateAndroid_onUpdateUrl(env,
288                                              obj.obj(),
289                                              java_url.obj());
290}
291
292void WebContentsDelegateAndroid::HandleKeyboardEvent(
293    WebContents* source,
294    const content::NativeWebKeyboardEvent& event) {
295  jobject key_event = event.os_event;
296  if (key_event) {
297    JNIEnv* env = AttachCurrentThread();
298    ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
299    if (obj.is_null())
300      return;
301    Java_WebContentsDelegateAndroid_handleKeyboardEvent(
302        env, obj.obj(), key_event);
303  }
304}
305
306bool WebContentsDelegateAndroid::TakeFocus(WebContents* source, bool reverse) {
307  JNIEnv* env = AttachCurrentThread();
308  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
309  if (obj.is_null())
310    return WebContentsDelegate::TakeFocus(source, reverse);
311  return Java_WebContentsDelegateAndroid_takeFocus(
312      env, obj.obj(), reverse);
313}
314
315void WebContentsDelegateAndroid::ShowRepostFormWarningDialog(
316    WebContents* source) {
317  JNIEnv* env = AttachCurrentThread();
318  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
319  if (obj.is_null())
320    return;
321  ScopedJavaLocalRef<jobject> content_view_core =
322      content::ContentViewCore::FromWebContents(source)->GetJavaObject();
323  if (content_view_core.is_null())
324    return;
325  Java_WebContentsDelegateAndroid_showRepostFormWarningDialog(env, obj.obj(),
326      content_view_core.obj());
327}
328
329void WebContentsDelegateAndroid::ToggleFullscreenModeForTab(
330    WebContents* web_contents,
331    bool enter_fullscreen) {
332  JNIEnv* env = AttachCurrentThread();
333  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
334  if (obj.is_null())
335    return;
336  Java_WebContentsDelegateAndroid_toggleFullscreenModeForTab(
337      env, obj.obj(), enter_fullscreen);
338}
339
340bool WebContentsDelegateAndroid::IsFullscreenForTabOrPending(
341    const WebContents* web_contents) const {
342  JNIEnv* env = AttachCurrentThread();
343  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
344  if (obj.is_null())
345    return false;
346  return Java_WebContentsDelegateAndroid_isFullscreenForTabOrPending(
347      env, obj.obj());
348}
349
350void WebContentsDelegateAndroid::ShowValidationMessage(
351    WebContents* web_contents,
352    const gfx::Rect& anchor_in_root_view,
353    const base::string16& main_text,
354    const base::string16& sub_text) {
355  RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
356  if (rwhv) {
357    validation_message_bubble_.reset(
358        new ValidationMessageBubbleAndroid(rwhv->GetRenderWidgetHost(),
359                                           anchor_in_root_view,
360                                           main_text,
361                                           sub_text));
362  }
363}
364
365void WebContentsDelegateAndroid::HideValidationMessage(
366    WebContents* web_contents) {
367  validation_message_bubble_.reset();
368}
369
370void WebContentsDelegateAndroid::MoveValidationMessage(
371    WebContents* web_contents,
372    const gfx::Rect& anchor_in_root_view) {
373  if (!validation_message_bubble_)
374    return;
375  RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
376  if (rwhv) {
377    validation_message_bubble_->SetPositionRelativeToAnchor(
378        rwhv->GetRenderWidgetHost(), anchor_in_root_view);
379  }
380}
381// ----------------------------------------------------------------------------
382// Native JNI methods
383// ----------------------------------------------------------------------------
384
385// Register native methods
386
387bool RegisterWebContentsDelegateAndroid(JNIEnv* env) {
388  return RegisterNativesImpl(env);
389}
390
391}  // namespace web_contents_delegate_android
392