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