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 "chrome/browser/android/tab_android.h" 6 7 #include "base/android/jni_android.h" 8 #include "base/android/jni_array.h" 9 #include "base/android/jni_string.h" 10 #include "base/debug/trace_event.h" 11 #include "chrome/browser/android/chrome_web_contents_delegate_android.h" 12 #include "chrome/browser/browser_about_handler.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/content_settings/tab_specific_content_settings.h" 15 #include "chrome/browser/google/google_url_tracker_factory.h" 16 #include "chrome/browser/infobars/infobar_service.h" 17 #include "chrome/browser/prerender/prerender_contents.h" 18 #include "chrome/browser/prerender/prerender_manager.h" 19 #include "chrome/browser/prerender/prerender_manager_factory.h" 20 #include "chrome/browser/printing/print_view_manager_basic.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/profiles/profile_android.h" 23 #include "chrome/browser/search/search.h" 24 #include "chrome/browser/sessions/session_tab_helper.h" 25 #include "chrome/browser/sync/glue/synced_tab_delegate_android.h" 26 #include "chrome/browser/ui/android/content_settings/popup_blocked_infobar_delegate.h" 27 #include "chrome/browser/ui/android/context_menu_helper.h" 28 #include "chrome/browser/ui/android/infobars/infobar_container_android.h" 29 #include "chrome/browser/ui/android/tab_model/tab_model.h" 30 #include "chrome/browser/ui/android/tab_model/tab_model_list.h" 31 #include "chrome/browser/ui/android/window_android_helper.h" 32 #include "chrome/browser/ui/blocked_content/popup_blocker_tab_helper.h" 33 #include "chrome/browser/ui/search/instant_search_prerenderer.h" 34 #include "chrome/browser/ui/search/search_tab_helper.h" 35 #include "chrome/browser/ui/tab_contents/core_tab_helper.h" 36 #include "chrome/browser/ui/tab_helpers.h" 37 #include "chrome/browser/ui/toolbar/toolbar_model_impl.h" 38 #include "chrome/common/url_constants.h" 39 #include "components/google/core/browser/google_url_tracker.h" 40 #include "components/google/core/browser/google_util.h" 41 #include "components/infobars/core/infobar_container.h" 42 #include "components/url_fixer/url_fixer.h" 43 #include "content/public/browser/android/content_view_core.h" 44 #include "content/public/browser/navigation_entry.h" 45 #include "content/public/browser/notification_service.h" 46 #include "content/public/browser/user_metrics.h" 47 #include "content/public/browser/web_contents.h" 48 #include "jni/Tab_jni.h" 49 #include "third_party/WebKit/public/platform/WebReferrerPolicy.h" 50 51 TabAndroid* TabAndroid::FromWebContents(content::WebContents* web_contents) { 52 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents); 53 if (!core_tab_helper) 54 return NULL; 55 56 CoreTabHelperDelegate* core_delegate = core_tab_helper->delegate(); 57 if (!core_delegate) 58 return NULL; 59 60 return static_cast<TabAndroid*>(core_delegate); 61 } 62 63 TabAndroid* TabAndroid::GetNativeTab(JNIEnv* env, jobject obj) { 64 return reinterpret_cast<TabAndroid*>(Java_Tab_getNativePtr(env, obj)); 65 } 66 67 void TabAndroid::AttachTabHelpers(content::WebContents* web_contents) { 68 DCHECK(web_contents); 69 70 TabHelpers::AttachTabHelpers(web_contents); 71 } 72 73 TabAndroid::TabAndroid(JNIEnv* env, jobject obj) 74 : weak_java_tab_(env, obj), 75 synced_tab_delegate_(new browser_sync::SyncedTabDelegateAndroid(this)) { 76 Java_Tab_setNativePtr(env, obj, reinterpret_cast<intptr_t>(this)); 77 } 78 79 TabAndroid::~TabAndroid() { 80 JNIEnv* env = base::android::AttachCurrentThread(); 81 ScopedJavaLocalRef<jobject> obj = weak_java_tab_.get(env); 82 if (obj.is_null()) 83 return; 84 85 Java_Tab_clearNativePtr(env, obj.obj()); 86 } 87 88 base::android::ScopedJavaLocalRef<jobject> TabAndroid::GetJavaObject() { 89 JNIEnv* env = base::android::AttachCurrentThread(); 90 return weak_java_tab_.get(env); 91 } 92 93 int TabAndroid::GetAndroidId() const { 94 JNIEnv* env = base::android::AttachCurrentThread(); 95 ScopedJavaLocalRef<jobject> obj = weak_java_tab_.get(env); 96 if (obj.is_null()) 97 return -1; 98 return Java_Tab_getId(env, obj.obj()); 99 } 100 101 int TabAndroid::GetSyncId() const { 102 JNIEnv* env = base::android::AttachCurrentThread(); 103 ScopedJavaLocalRef<jobject> obj = weak_java_tab_.get(env); 104 if (obj.is_null()) 105 return 0; 106 return Java_Tab_getSyncId(env, obj.obj()); 107 } 108 109 base::string16 TabAndroid::GetTitle() const { 110 JNIEnv* env = base::android::AttachCurrentThread(); 111 ScopedJavaLocalRef<jobject> obj = weak_java_tab_.get(env); 112 if (obj.is_null()) 113 return base::string16(); 114 return base::android::ConvertJavaStringToUTF16( 115 Java_Tab_getTitle(env, obj.obj())); 116 } 117 118 GURL TabAndroid::GetURL() const { 119 JNIEnv* env = base::android::AttachCurrentThread(); 120 ScopedJavaLocalRef<jobject> obj = weak_java_tab_.get(env); 121 if (obj.is_null()) 122 return GURL::EmptyGURL(); 123 return GURL(base::android::ConvertJavaStringToUTF8( 124 Java_Tab_getUrl(env, obj.obj()))); 125 } 126 127 bool TabAndroid::LoadIfNeeded() { 128 JNIEnv* env = base::android::AttachCurrentThread(); 129 ScopedJavaLocalRef<jobject> obj = weak_java_tab_.get(env); 130 if (obj.is_null()) 131 return false; 132 return Java_Tab_loadIfNeeded(env, obj.obj()); 133 } 134 135 content::ContentViewCore* TabAndroid::GetContentViewCore() const { 136 if (!web_contents()) 137 return NULL; 138 139 return content::ContentViewCore::FromWebContents(web_contents()); 140 } 141 142 Profile* TabAndroid::GetProfile() const { 143 if (!web_contents()) 144 return NULL; 145 146 return Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 147 } 148 149 browser_sync::SyncedTabDelegate* TabAndroid::GetSyncedTabDelegate() const { 150 return synced_tab_delegate_.get(); 151 } 152 153 void TabAndroid::SetWindowSessionID(SessionID::id_type window_id) { 154 session_window_id_.set_id(window_id); 155 156 if (!web_contents()) 157 return; 158 159 SessionTabHelper* session_tab_helper = 160 SessionTabHelper::FromWebContents(web_contents()); 161 session_tab_helper->SetWindowID(session_window_id_); 162 } 163 164 void TabAndroid::SetSyncId(int sync_id) { 165 JNIEnv* env = base::android::AttachCurrentThread(); 166 ScopedJavaLocalRef<jobject> obj = weak_java_tab_.get(env); 167 if (obj.is_null()) 168 return; 169 Java_Tab_setSyncId(env, obj.obj(), sync_id); 170 } 171 172 void TabAndroid::HandlePopupNavigation(chrome::NavigateParams* params) { 173 NOTIMPLEMENTED(); 174 } 175 176 void TabAndroid::OnReceivedHttpAuthRequest(jobject auth_handler, 177 const base::string16& host, 178 const base::string16& realm) { 179 NOTIMPLEMENTED(); 180 } 181 182 bool TabAndroid::ShouldWelcomePageLinkToTermsOfService() { 183 NOTIMPLEMENTED(); 184 return false; 185 } 186 187 bool TabAndroid::HasPrerenderedUrl(GURL gurl) { 188 prerender::PrerenderManager* prerender_manager = GetPrerenderManager(); 189 if (!prerender_manager) 190 return false; 191 192 std::vector<content::WebContents*> contents = 193 prerender_manager->GetAllPrerenderingContents(); 194 prerender::PrerenderContents* prerender_contents; 195 for (size_t i = 0; i < contents.size(); ++i) { 196 prerender_contents = prerender_manager-> 197 GetPrerenderContents(contents.at(i)); 198 if (prerender_contents->prerender_url() == gurl && 199 prerender_contents->has_finished_loading()) { 200 return true; 201 } 202 } 203 return false; 204 } 205 206 void TabAndroid::SwapTabContents(content::WebContents* old_contents, 207 content::WebContents* new_contents, 208 bool did_start_load, 209 bool did_finish_load) { 210 JNIEnv* env = base::android::AttachCurrentThread(); 211 212 // We need to notify the native InfobarContainer so infobars can be swapped. 213 InfoBarContainerAndroid* infobar_container = 214 reinterpret_cast<InfoBarContainerAndroid*>( 215 Java_Tab_getNativeInfoBarContainer( 216 env, 217 weak_java_tab_.get(env).obj())); 218 InfoBarService* new_infobar_service = 219 new_contents ? InfoBarService::FromWebContents(new_contents) : NULL; 220 infobar_container->ChangeInfoBarManager(new_infobar_service); 221 222 Java_Tab_swapWebContents( 223 env, 224 weak_java_tab_.get(env).obj(), 225 reinterpret_cast<intptr_t>(new_contents), 226 did_start_load, 227 did_finish_load); 228 } 229 230 void TabAndroid::Observe(int type, 231 const content::NotificationSource& source, 232 const content::NotificationDetails& details) { 233 JNIEnv* env = base::android::AttachCurrentThread(); 234 switch (type) { 235 case chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED: { 236 TabSpecificContentSettings* settings = 237 TabSpecificContentSettings::FromWebContents(web_contents()); 238 if (!settings->IsBlockageIndicated(CONTENT_SETTINGS_TYPE_POPUPS)) { 239 // TODO(dfalcantara): Create an InfoBarDelegate to keep the 240 // PopupBlockedInfoBar logic native-side instead of straddling the JNI 241 // boundary. 242 int num_popups = 0; 243 PopupBlockerTabHelper* popup_blocker_helper = 244 PopupBlockerTabHelper::FromWebContents(web_contents()); 245 if (popup_blocker_helper) 246 num_popups = popup_blocker_helper->GetBlockedPopupsCount(); 247 248 if (num_popups > 0) 249 PopupBlockedInfoBarDelegate::Create(web_contents(), num_popups); 250 251 settings->SetBlockageHasBeenIndicated(CONTENT_SETTINGS_TYPE_POPUPS); 252 } 253 break; 254 } 255 case chrome::NOTIFICATION_FAVICON_UPDATED: 256 Java_Tab_onFaviconUpdated(env, weak_java_tab_.get(env).obj()); 257 break; 258 case content::NOTIFICATION_NAV_ENTRY_CHANGED: 259 Java_Tab_onNavEntryChanged(env, weak_java_tab_.get(env).obj()); 260 break; 261 default: 262 NOTREACHED() << "Unexpected notification " << type; 263 break; 264 } 265 } 266 267 void TabAndroid::Destroy(JNIEnv* env, jobject obj) { 268 delete this; 269 } 270 271 void TabAndroid::InitWebContents(JNIEnv* env, 272 jobject obj, 273 jboolean incognito, 274 jobject jcontent_view_core, 275 jobject jweb_contents_delegate, 276 jobject jcontext_menu_populator) { 277 content::ContentViewCore* content_view_core = 278 content::ContentViewCore::GetNativeContentViewCore(env, 279 jcontent_view_core); 280 DCHECK(content_view_core); 281 DCHECK(content_view_core->GetWebContents()); 282 283 web_contents_.reset(content_view_core->GetWebContents()); 284 AttachTabHelpers(web_contents_.get()); 285 286 SetWindowSessionID(session_window_id_.id()); 287 288 session_tab_id_.set_id( 289 SessionTabHelper::FromWebContents(web_contents())->session_id().id()); 290 ContextMenuHelper::FromWebContents(web_contents())->SetPopulator( 291 jcontext_menu_populator); 292 WindowAndroidHelper::FromWebContents(web_contents())-> 293 SetWindowAndroid(content_view_core->GetWindowAndroid()); 294 CoreTabHelper::FromWebContents(web_contents())->set_delegate(this); 295 web_contents_delegate_.reset( 296 new chrome::android::ChromeWebContentsDelegateAndroid( 297 env, jweb_contents_delegate)); 298 web_contents_delegate_->LoadProgressChanged(web_contents(), 0); 299 web_contents()->SetDelegate(web_contents_delegate_.get()); 300 301 notification_registrar_.Add( 302 this, 303 chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED, 304 content::Source<content::WebContents>(web_contents())); 305 notification_registrar_.Add( 306 this, 307 chrome::NOTIFICATION_FAVICON_UPDATED, 308 content::Source<content::WebContents>(web_contents())); 309 notification_registrar_.Add( 310 this, 311 content::NOTIFICATION_NAV_ENTRY_CHANGED, 312 content::Source<content::NavigationController>( 313 &web_contents()->GetController())); 314 315 synced_tab_delegate_->SetWebContents(web_contents()); 316 317 // Verify that the WebContents this tab represents matches the expected 318 // off the record state. 319 CHECK_EQ(GetProfile()->IsOffTheRecord(), incognito); 320 } 321 322 void TabAndroid::DestroyWebContents(JNIEnv* env, 323 jobject obj, 324 jboolean delete_native) { 325 DCHECK(web_contents()); 326 327 notification_registrar_.Remove( 328 this, 329 chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED, 330 content::Source<content::WebContents>(web_contents())); 331 notification_registrar_.Remove( 332 this, 333 chrome::NOTIFICATION_FAVICON_UPDATED, 334 content::Source<content::WebContents>(web_contents())); 335 notification_registrar_.Remove( 336 this, 337 content::NOTIFICATION_NAV_ENTRY_CHANGED, 338 content::Source<content::NavigationController>( 339 &web_contents()->GetController())); 340 341 web_contents()->SetDelegate(NULL); 342 343 if (delete_native) { 344 web_contents_.reset(); 345 synced_tab_delegate_->ResetWebContents(); 346 } else { 347 // Release the WebContents so it does not get deleted by the scoped_ptr. 348 ignore_result(web_contents_.release()); 349 } 350 } 351 352 base::android::ScopedJavaLocalRef<jobject> TabAndroid::GetWebContents( 353 JNIEnv* env, 354 jobject obj) { 355 if (!web_contents_.get()) 356 return base::android::ScopedJavaLocalRef<jobject>(); 357 return web_contents_->GetJavaWebContents(); 358 } 359 360 base::android::ScopedJavaLocalRef<jobject> TabAndroid::GetProfileAndroid( 361 JNIEnv* env, 362 jobject obj) { 363 Profile* profile = GetProfile(); 364 if (!profile) 365 return base::android::ScopedJavaLocalRef<jobject>(); 366 ProfileAndroid* profile_android = ProfileAndroid::FromProfile(profile); 367 if (!profile_android) 368 return base::android::ScopedJavaLocalRef<jobject>(); 369 370 return profile_android->GetJavaObject(); 371 } 372 373 TabAndroid::TabLoadStatus TabAndroid::LoadUrl(JNIEnv* env, 374 jobject obj, 375 jstring url, 376 jstring j_extra_headers, 377 jbyteArray j_post_data, 378 jint page_transition, 379 jstring j_referrer_url, 380 jint referrer_policy, 381 jboolean is_renderer_initiated) { 382 content::ContentViewCore* content_view = GetContentViewCore(); 383 if (!content_view) 384 return PAGE_LOAD_FAILED; 385 386 GURL gurl(base::android::ConvertJavaStringToUTF8(env, url)); 387 if (gurl.is_empty()) 388 return PAGE_LOAD_FAILED; 389 390 // If the page was prerendered, use it. 391 // Note in incognito mode, we don't have a PrerenderManager. 392 393 prerender::PrerenderManager* prerender_manager = 394 prerender::PrerenderManagerFactory::GetForProfile(GetProfile()); 395 if (prerender_manager) { 396 bool prefetched_page_loaded = HasPrerenderedUrl(gurl); 397 // Getting the load status before MaybeUsePrerenderedPage() b/c it resets. 398 chrome::NavigateParams params(NULL, web_contents()); 399 InstantSearchPrerenderer* prerenderer = 400 InstantSearchPrerenderer::GetForProfile(GetProfile()); 401 if (prerenderer) { 402 const base::string16& search_terms = 403 chrome::ExtractSearchTermsFromURL(GetProfile(), gurl); 404 if (!search_terms.empty() && 405 prerenderer->CanCommitQuery(web_contents_.get(), search_terms)) { 406 prerenderer->Commit(search_terms); 407 408 if (prerenderer->UsePrerenderedPage(gurl, ¶ms)) 409 return FULL_PRERENDERED_PAGE_LOAD; 410 } 411 prerenderer->Cancel(); 412 } 413 if (prerender_manager->MaybeUsePrerenderedPage(gurl, ¶ms)) { 414 return prefetched_page_loaded ? 415 FULL_PRERENDERED_PAGE_LOAD : PARTIAL_PRERENDERED_PAGE_LOAD; 416 } 417 } 418 419 GURL fixed_url( 420 url_fixer::FixupURL(gurl.possibly_invalid_spec(), std::string())); 421 if (!fixed_url.is_valid()) 422 return PAGE_LOAD_FAILED; 423 424 if (!HandleNonNavigationAboutURL(fixed_url)) { 425 // Notify the GoogleURLTracker of searches, it might want to change the 426 // actual Google site used (for instance when in the UK, google.co.uk, when 427 // in the US google.com). 428 // Note that this needs to happen before we initiate the navigation as the 429 // GoogleURLTracker uses the navigation pending notification to trigger the 430 // infobar. 431 if (google_util::IsGoogleSearchUrl(fixed_url) && 432 (page_transition & content::PAGE_TRANSITION_GENERATED)) { 433 GoogleURLTracker* tracker = 434 GoogleURLTrackerFactory::GetForProfile(GetProfile()); 435 if (tracker) 436 tracker->SearchCommitted(); 437 } 438 439 // Record UMA "ShowHistory" here. That way it'll pick up both user 440 // typing chrome://history as well as selecting from the drop down menu. 441 if (fixed_url.spec() == chrome::kChromeUIHistoryURL) { 442 content::RecordAction(base::UserMetricsAction("ShowHistory")); 443 } 444 445 content::NavigationController::LoadURLParams load_params(fixed_url); 446 if (j_extra_headers) { 447 load_params.extra_headers = base::android::ConvertJavaStringToUTF8( 448 env, 449 j_extra_headers); 450 } 451 if (j_post_data) { 452 load_params.load_type = 453 content::NavigationController::LOAD_TYPE_BROWSER_INITIATED_HTTP_POST; 454 std::vector<uint8> post_data; 455 base::android::JavaByteArrayToByteVector(env, j_post_data, &post_data); 456 load_params.browser_initiated_post_data = 457 base::RefCountedBytes::TakeVector(&post_data); 458 } 459 load_params.transition_type = 460 content::PageTransitionFromInt(page_transition); 461 if (j_referrer_url) { 462 load_params.referrer = content::Referrer( 463 GURL(base::android::ConvertJavaStringToUTF8(env, j_referrer_url)), 464 static_cast<blink::WebReferrerPolicy>(referrer_policy)); 465 } 466 const base::string16 search_terms = 467 chrome::ExtractSearchTermsFromURL(GetProfile(), gurl); 468 SearchTabHelper* search_tab_helper = 469 SearchTabHelper::FromWebContents(web_contents_.get()); 470 if (!search_terms.empty() && search_tab_helper && 471 search_tab_helper->SupportsInstant()) { 472 search_tab_helper->Submit(search_terms); 473 return DEFAULT_PAGE_LOAD; 474 } 475 load_params.is_renderer_initiated = is_renderer_initiated; 476 content_view->LoadUrl(load_params); 477 } 478 return DEFAULT_PAGE_LOAD; 479 } 480 481 ToolbarModel::SecurityLevel TabAndroid::GetSecurityLevel(JNIEnv* env, 482 jobject obj) { 483 return ToolbarModelImpl::GetSecurityLevelForWebContents(web_contents()); 484 } 485 486 void TabAndroid::SetActiveNavigationEntryTitleForUrl(JNIEnv* env, 487 jobject obj, 488 jstring jurl, 489 jstring jtitle) { 490 DCHECK(web_contents()); 491 492 base::string16 title; 493 if (jtitle) 494 title = base::android::ConvertJavaStringToUTF16(env, jtitle); 495 496 std::string url; 497 if (jurl) 498 url = base::android::ConvertJavaStringToUTF8(env, jurl); 499 500 content::NavigationEntry* entry = 501 web_contents()->GetController().GetVisibleEntry(); 502 if (entry && url == entry->GetVirtualURL().spec()) 503 entry->SetTitle(title); 504 } 505 506 bool TabAndroid::Print(JNIEnv* env, jobject obj) { 507 if (!web_contents()) 508 return false; 509 510 printing::PrintViewManagerBasic::CreateForWebContents(web_contents()); 511 printing::PrintViewManagerBasic* print_view_manager = 512 printing::PrintViewManagerBasic::FromWebContents(web_contents()); 513 if (print_view_manager == NULL) 514 return false; 515 516 print_view_manager->PrintNow(); 517 return true; 518 } 519 520 prerender::PrerenderManager* TabAndroid::GetPrerenderManager() const { 521 Profile* profile = GetProfile(); 522 if (!profile) 523 return NULL; 524 return prerender::PrerenderManagerFactory::GetForProfile(profile); 525 } 526 527 static void Init(JNIEnv* env, jobject obj) { 528 TRACE_EVENT0("native", "TabAndroid::Init"); 529 // This will automatically bind to the Java object and pass ownership there. 530 new TabAndroid(env, obj); 531 } 532 533 bool TabAndroid::RegisterTabAndroid(JNIEnv* env) { 534 return RegisterNativesImpl(env); 535 } 536