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 "content/browser/android/web_contents_observer_android.h"
6
7#include <string>
8
9#include <jni.h>
10
11#include "base/android/jni_android.h"
12#include "base/android/jni_string.h"
13#include "base/android/scoped_java_ref.h"
14#include "content/browser/renderer_host/render_widget_host_impl.h"
15#include "content/browser/web_contents/web_contents_impl.h"
16#include "content/public/browser/navigation_details.h"
17#include "content/public/browser/navigation_entry.h"
18#include "jni/WebContentsObserverAndroid_jni.h"
19
20using base::android::AttachCurrentThread;
21using base::android::ScopedJavaLocalRef;
22using base::android::ConvertUTF8ToJavaString;
23using base::android::ConvertUTF16ToJavaString;
24
25namespace content {
26
27// TODO(dcheng): File a bug. This class incorrectly passes just a frame ID,
28// which is not sufficient to identify a frame (since frame IDs are scoped per
29// render process, and so may collide).
30WebContentsObserverAndroid::WebContentsObserverAndroid(
31    JNIEnv* env,
32    jobject obj,
33    WebContents* web_contents)
34    : WebContentsObserver(web_contents),
35      weak_java_observer_(env, obj){
36}
37
38WebContentsObserverAndroid::~WebContentsObserverAndroid() {
39}
40
41jlong Init(JNIEnv* env, jobject obj, jobject java_web_contents) {
42  WebContents* web_contents =
43      WebContents::FromJavaWebContents(java_web_contents);
44  CHECK(web_contents);
45
46  WebContentsObserverAndroid* native_observer = new WebContentsObserverAndroid(
47      env, obj, web_contents);
48  return reinterpret_cast<intptr_t>(native_observer);
49}
50
51void WebContentsObserverAndroid::Destroy(JNIEnv* env, jobject obj) {
52  delete this;
53}
54
55void WebContentsObserverAndroid::WebContentsDestroyed() {
56  JNIEnv* env = AttachCurrentThread();
57  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
58  if (obj.is_null()) {
59    delete this;
60  } else {
61    // The java side will destroy |this|
62    Java_WebContentsObserverAndroid_detachFromWebContents(env, obj.obj());
63  }
64}
65
66void WebContentsObserverAndroid::RenderProcessGone(
67    base::TerminationStatus termination_status) {
68  JNIEnv* env = AttachCurrentThread();
69  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
70  if (obj.is_null())
71    return;
72  jboolean was_oom_protected =
73      termination_status == base::TERMINATION_STATUS_OOM_PROTECTED;
74  Java_WebContentsObserverAndroid_renderProcessGone(
75      env, obj.obj(), was_oom_protected);
76}
77
78void WebContentsObserverAndroid::DidStartLoading(
79    RenderViewHost* render_view_host) {
80  JNIEnv* env = AttachCurrentThread();
81  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
82  if (obj.is_null())
83    return;
84  ScopedJavaLocalRef<jstring> jstring_url(ConvertUTF8ToJavaString(
85      env, web_contents()->GetVisibleURL().spec()));
86  Java_WebContentsObserverAndroid_didStartLoading(
87      env, obj.obj(), jstring_url.obj());
88}
89
90void WebContentsObserverAndroid::DidStopLoading(
91    RenderViewHost* render_view_host) {
92  JNIEnv* env = AttachCurrentThread();
93  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
94  if (obj.is_null())
95    return;
96  ScopedJavaLocalRef<jstring> jstring_url(ConvertUTF8ToJavaString(
97      env, web_contents()->GetLastCommittedURL().spec()));
98  Java_WebContentsObserverAndroid_didStopLoading(
99      env, obj.obj(), jstring_url.obj());
100}
101
102void WebContentsObserverAndroid::DidFailProvisionalLoad(
103    RenderFrameHost* render_frame_host,
104    const GURL& validated_url,
105    int error_code,
106    const base::string16& error_description) {
107  DidFailLoadInternal(true,
108                      !render_frame_host->GetParent(),
109                      error_code,
110                      error_description,
111                      validated_url);
112}
113
114void WebContentsObserverAndroid::DidFailLoad(
115    RenderFrameHost* render_frame_host,
116    const GURL& validated_url,
117    int error_code,
118    const base::string16& error_description) {
119  DidFailLoadInternal(false,
120                      !render_frame_host->GetParent(),
121                      error_code,
122                      error_description,
123                      validated_url);
124}
125
126void WebContentsObserverAndroid::DidNavigateMainFrame(
127    const LoadCommittedDetails& details,
128    const FrameNavigateParams& params) {
129  JNIEnv* env = AttachCurrentThread();
130  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
131  if (obj.is_null())
132    return;
133  ScopedJavaLocalRef<jstring> jstring_url(
134      ConvertUTF8ToJavaString(env, params.url.spec()));
135  ScopedJavaLocalRef<jstring> jstring_base_url(
136      ConvertUTF8ToJavaString(env, params.base_url.spec()));
137
138  // See http://crbug.com/251330 for why it's determined this way.
139  url::Replacements<char> replacements;
140  replacements.ClearRef();
141  bool urls_same_ignoring_fragment =
142      params.url.ReplaceComponents(replacements) ==
143      details.previous_url.ReplaceComponents(replacements);
144
145  // is_fragment_navigation is indicative of the intent of this variable.
146  // However, there isn't sufficient information here to determine whether this
147  // is actually a fragment navigation, or a history API navigation to a URL
148  // that would also be valid for a fragment navigation.
149  bool is_fragment_navigation = urls_same_ignoring_fragment &&
150      (details.type == NAVIGATION_TYPE_IN_PAGE || details.is_in_page);
151  Java_WebContentsObserverAndroid_didNavigateMainFrame(
152      env, obj.obj(), jstring_url.obj(), jstring_base_url.obj(),
153      details.is_navigation_to_different_page(), is_fragment_navigation,
154      details.http_status_code);
155}
156
157void WebContentsObserverAndroid::DidNavigateAnyFrame(
158    const LoadCommittedDetails& details,
159    const FrameNavigateParams& params) {
160  JNIEnv* env = AttachCurrentThread();
161  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
162  if (obj.is_null())
163    return;
164  ScopedJavaLocalRef<jstring> jstring_url(
165      ConvertUTF8ToJavaString(env, params.url.spec()));
166  ScopedJavaLocalRef<jstring> jstring_base_url(
167      ConvertUTF8ToJavaString(env, params.base_url.spec()));
168  jboolean jboolean_is_reload = ui::PageTransitionCoreTypeIs(
169      params.transition, ui::PAGE_TRANSITION_RELOAD);
170
171  Java_WebContentsObserverAndroid_didNavigateAnyFrame(
172      env, obj.obj(), jstring_url.obj(), jstring_base_url.obj(),
173      jboolean_is_reload);
174}
175
176void WebContentsObserverAndroid::DidStartProvisionalLoadForFrame(
177    RenderFrameHost* render_frame_host,
178    const GURL& validated_url,
179    bool is_error_page,
180    bool is_iframe_srcdoc) {
181  JNIEnv* env = AttachCurrentThread();
182  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
183  if (obj.is_null())
184    return;
185  ScopedJavaLocalRef<jstring> jstring_url(
186      ConvertUTF8ToJavaString(env, validated_url.spec()));
187  // TODO(dcheng): Does Java really need the parent frame ID? It doesn't appear
188  // to be used at all, and it just adds complexity here.
189  Java_WebContentsObserverAndroid_didStartProvisionalLoadForFrame(
190      env,
191      obj.obj(),
192      render_frame_host->GetRoutingID(),
193      render_frame_host->GetParent()
194          ? render_frame_host->GetParent()->GetRoutingID()
195          : -1,
196      !render_frame_host->GetParent(),
197      jstring_url.obj(),
198      is_error_page,
199      is_iframe_srcdoc);
200}
201
202void WebContentsObserverAndroid::DidCommitProvisionalLoadForFrame(
203    RenderFrameHost* render_frame_host,
204    const GURL& url,
205    ui::PageTransition transition_type) {
206  JNIEnv* env = AttachCurrentThread();
207  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
208  if (obj.is_null())
209    return;
210  ScopedJavaLocalRef<jstring> jstring_url(
211      ConvertUTF8ToJavaString(env, url.spec()));
212  Java_WebContentsObserverAndroid_didCommitProvisionalLoadForFrame(
213      env,
214      obj.obj(),
215      render_frame_host->GetRoutingID(),
216      !render_frame_host->GetParent(),
217      jstring_url.obj(),
218      transition_type);
219}
220
221void WebContentsObserverAndroid::DidFinishLoad(
222    RenderFrameHost* render_frame_host,
223    const GURL& validated_url) {
224  JNIEnv* env = AttachCurrentThread();
225  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
226  if (obj.is_null())
227    return;
228
229  std::string url_string = validated_url.spec();
230  NavigationEntry* entry =
231    web_contents()->GetController().GetLastCommittedEntry();
232  // Note that GetBaseURLForDataURL is only used by the Android WebView.
233  if (entry && !entry->GetBaseURLForDataURL().is_empty())
234    url_string = entry->GetBaseURLForDataURL().possibly_invalid_spec();
235
236  ScopedJavaLocalRef<jstring> jstring_url(
237      ConvertUTF8ToJavaString(env, url_string));
238  Java_WebContentsObserverAndroid_didFinishLoad(
239      env,
240      obj.obj(),
241      render_frame_host->GetRoutingID(),
242      jstring_url.obj(),
243      !render_frame_host->GetParent());
244}
245
246void WebContentsObserverAndroid::DocumentLoadedInFrame(
247    RenderFrameHost* render_frame_host) {
248  JNIEnv* env = AttachCurrentThread();
249  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
250  if (obj.is_null())
251    return;
252  Java_WebContentsObserverAndroid_documentLoadedInFrame(
253      env, obj.obj(), render_frame_host->GetRoutingID());
254}
255
256void WebContentsObserverAndroid::NavigationEntryCommitted(
257    const LoadCommittedDetails& load_details) {
258  JNIEnv* env = AttachCurrentThread();
259  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
260  if (obj.is_null())
261    return;
262  Java_WebContentsObserverAndroid_navigationEntryCommitted(env, obj.obj());
263}
264
265void WebContentsObserverAndroid::DidAttachInterstitialPage() {
266  JNIEnv* env = AttachCurrentThread();
267  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
268  if (obj.is_null())
269    return;
270  Java_WebContentsObserverAndroid_didAttachInterstitialPage(env, obj.obj());
271}
272
273void WebContentsObserverAndroid::DidDetachInterstitialPage() {
274  JNIEnv* env = AttachCurrentThread();
275  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
276  if (obj.is_null())
277    return;
278  Java_WebContentsObserverAndroid_didDetachInterstitialPage(env, obj.obj());
279}
280
281void WebContentsObserverAndroid::DidChangeThemeColor(SkColor color) {
282  JNIEnv* env = AttachCurrentThread();
283  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
284  if (obj.is_null())
285    return;
286  Java_WebContentsObserverAndroid_didChangeThemeColor(env, obj.obj(), color);
287}
288
289void WebContentsObserverAndroid::DidFailLoadInternal(
290    bool is_provisional_load,
291    bool is_main_frame,
292    int error_code,
293    const base::string16& description,
294    const GURL& url) {
295  JNIEnv* env = AttachCurrentThread();
296  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
297  if (obj.is_null())
298    return;
299  ScopedJavaLocalRef<jstring> jstring_error_description(
300      ConvertUTF16ToJavaString(env, description));
301  ScopedJavaLocalRef<jstring> jstring_url(
302      ConvertUTF8ToJavaString(env, url.spec()));
303
304  Java_WebContentsObserverAndroid_didFailLoad(
305      env, obj.obj(),
306      is_provisional_load,
307      is_main_frame,
308      error_code,
309      jstring_error_description.obj(), jstring_url.obj());
310}
311
312void WebContentsObserverAndroid::DidFirstVisuallyNonEmptyPaint() {
313  JNIEnv* env = AttachCurrentThread();
314  ScopedJavaLocalRef<jobject> obj(weak_java_observer_.get(env));
315  if (obj.is_null())
316    return;
317  Java_WebContentsObserverAndroid_didFirstVisuallyNonEmptyPaint(
318      env, obj.obj());
319}
320
321bool RegisterWebContentsObserverAndroid(JNIEnv* env) {
322  return RegisterNativesImpl(env);
323}
324}  // namespace content
325