// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/android/tab_android.h" #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/debug/trace_event.h" #include "chrome/browser/android/chrome_web_contents_delegate_android.h" #include "chrome/browser/browser_about_handler.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/content_settings/tab_specific_content_settings.h" #include "chrome/browser/google/google_url_tracker.h" #include "chrome/browser/google/google_util.h" #include "chrome/browser/prerender/prerender_contents.h" #include "chrome/browser/prerender/prerender_manager.h" #include "chrome/browser/prerender/prerender_manager_factory.h" #include "chrome/browser/printing/print_view_manager_basic.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_android.h" #include "chrome/browser/sessions/session_tab_helper.h" #include "chrome/browser/sync/glue/synced_tab_delegate_android.h" #include "chrome/browser/ui/android/content_settings/popup_blocked_infobar_delegate.h" #include "chrome/browser/ui/android/context_menu_helper.h" #include "chrome/browser/ui/android/infobars/infobar_container_android.h" #include "chrome/browser/ui/android/tab_model/tab_model.h" #include "chrome/browser/ui/android/tab_model/tab_model_list.h" #include "chrome/browser/ui/android/window_android_helper.h" #include "chrome/browser/ui/blocked_content/popup_blocker_tab_helper.h" #include "chrome/browser/ui/tab_contents/core_tab_helper.h" #include "chrome/browser/ui/tab_helpers.h" #include "chrome/browser/ui/toolbar/toolbar_model_impl.h" #include "chrome/common/net/url_fixer_upper.h" #include "chrome/common/url_constants.h" #include "content/public/browser/android/content_view_core.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/user_metrics.h" #include "content/public/browser/web_contents.h" #include "jni/Tab_jni.h" #include "third_party/WebKit/public/platform/WebReferrerPolicy.h" TabAndroid* TabAndroid::FromWebContents(content::WebContents* web_contents) { CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents); if (!core_tab_helper) return NULL; CoreTabHelperDelegate* core_delegate = core_tab_helper->delegate(); if (!core_delegate) return NULL; return static_cast(core_delegate); } TabAndroid* TabAndroid::GetNativeTab(JNIEnv* env, jobject obj) { return reinterpret_cast(Java_Tab_getNativePtr(env, obj)); } TabAndroid::TabAndroid(JNIEnv* env, jobject obj) : weak_java_tab_(env, obj), synced_tab_delegate_(new browser_sync::SyncedTabDelegateAndroid(this)) { Java_Tab_setNativePtr(env, obj, reinterpret_cast(this)); } TabAndroid::~TabAndroid() { JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = weak_java_tab_.get(env); if (obj.is_null()) return; Java_Tab_clearNativePtr(env, obj.obj()); } base::android::ScopedJavaLocalRef TabAndroid::GetJavaObject() { JNIEnv* env = base::android::AttachCurrentThread(); return weak_java_tab_.get(env); } int TabAndroid::GetAndroidId() const { JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = weak_java_tab_.get(env); if (obj.is_null()) return -1; return Java_Tab_getId(env, obj.obj()); } int TabAndroid::GetSyncId() const { JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = weak_java_tab_.get(env); if (obj.is_null()) return 0; return Java_Tab_getSyncId(env, obj.obj()); } base::string16 TabAndroid::GetTitle() const { JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = weak_java_tab_.get(env); if (obj.is_null()) return base::string16(); return base::android::ConvertJavaStringToUTF16( Java_Tab_getTitle(env, obj.obj())); } GURL TabAndroid::GetURL() const { JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = weak_java_tab_.get(env); if (obj.is_null()) return GURL::EmptyGURL(); return GURL(base::android::ConvertJavaStringToUTF8( Java_Tab_getUrl(env, obj.obj()))); } bool TabAndroid::RestoreIfNeeded() { JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = weak_java_tab_.get(env); if (obj.is_null()) return false; return Java_Tab_restoreIfNeeded(env, obj.obj()); } content::ContentViewCore* TabAndroid::GetContentViewCore() const { if (!web_contents()) return NULL; return content::ContentViewCore::FromWebContents(web_contents()); } Profile* TabAndroid::GetProfile() const { if (!web_contents()) return NULL; return Profile::FromBrowserContext(web_contents()->GetBrowserContext()); } browser_sync::SyncedTabDelegate* TabAndroid::GetSyncedTabDelegate() const { return synced_tab_delegate_.get(); } void TabAndroid::SetWindowSessionID(SessionID::id_type window_id) { session_window_id_.set_id(window_id); if (!web_contents()) return; SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(web_contents()); session_tab_helper->SetWindowID(session_window_id_); } void TabAndroid::SetSyncId(int sync_id) { JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef obj = weak_java_tab_.get(env); if (obj.is_null()) return; Java_Tab_setSyncId(env, obj.obj(), sync_id); } void TabAndroid::HandlePopupNavigation(chrome::NavigateParams* params) { NOTIMPLEMENTED(); } void TabAndroid::OnReceivedHttpAuthRequest(jobject auth_handler, const base::string16& host, const base::string16& realm) { NOTIMPLEMENTED(); } void TabAndroid::AddShortcutToBookmark(const GURL& url, const base::string16& title, const SkBitmap& skbitmap, int r_value, int g_value, int b_value) { NOTREACHED(); } void TabAndroid::EditBookmark(int64 node_id, const base::string16& node_title, bool is_folder, bool is_partner_bookmark) { NOTREACHED(); } void TabAndroid::OnNewTabPageReady() { NOTREACHED(); } bool TabAndroid::ShouldWelcomePageLinkToTermsOfService() { NOTIMPLEMENTED(); return false; } bool TabAndroid::HasPrerenderedUrl(GURL gurl) { prerender::PrerenderManager* prerender_manager = GetPrerenderManager(); if (!prerender_manager) return false; std::vector contents = prerender_manager->GetAllPrerenderingContents(); prerender::PrerenderContents* prerender_contents; for (size_t i = 0; i < contents.size(); ++i) { prerender_contents = prerender_manager-> GetPrerenderContents(contents.at(i)); if (prerender_contents->prerender_url() == gurl && prerender_contents->has_finished_loading()) { return true; } } return false; } void TabAndroid::SwapTabContents(content::WebContents* old_contents, content::WebContents* new_contents, bool did_start_load, bool did_finish_load) { JNIEnv* env = base::android::AttachCurrentThread(); // We need to notify the native InfobarContainer so infobars can be swapped. InfoBarContainerAndroid* infobar_container = reinterpret_cast( Java_Tab_getNativeInfoBarContainer( env, weak_java_tab_.get(env).obj())); InfoBarService* new_infobar_service = new_contents ? InfoBarService::FromWebContents(new_contents) : NULL; infobar_container->ChangeInfoBarService(new_infobar_service); Java_Tab_swapWebContents( env, weak_java_tab_.get(env).obj(), reinterpret_cast(new_contents), did_start_load, did_finish_load); } void TabAndroid::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { JNIEnv* env = base::android::AttachCurrentThread(); switch (type) { case chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED: { TabSpecificContentSettings* settings = TabSpecificContentSettings::FromWebContents(web_contents()); if (!settings->IsBlockageIndicated(CONTENT_SETTINGS_TYPE_POPUPS)) { // TODO(dfalcantara): Create an InfoBarDelegate to keep the // PopupBlockedInfoBar logic native-side instead of straddling the JNI // boundary. int num_popups = 0; PopupBlockerTabHelper* popup_blocker_helper = PopupBlockerTabHelper::FromWebContents(web_contents()); if (popup_blocker_helper) num_popups = popup_blocker_helper->GetBlockedPopupsCount(); if (num_popups > 0) PopupBlockedInfoBarDelegate::Create(web_contents(), num_popups); settings->SetBlockageHasBeenIndicated(CONTENT_SETTINGS_TYPE_POPUPS); } break; } case chrome::NOTIFICATION_FAVICON_UPDATED: Java_Tab_onFaviconUpdated(env, weak_java_tab_.get(env).obj()); break; case content::NOTIFICATION_NAV_ENTRY_CHANGED: Java_Tab_onNavEntryChanged(env, weak_java_tab_.get(env).obj()); break; default: NOTREACHED() << "Unexpected notification " << type; break; } } void TabAndroid::Destroy(JNIEnv* env, jobject obj) { delete this; } void TabAndroid::InitWebContents(JNIEnv* env, jobject obj, jboolean incognito, jobject jcontent_view_core, jobject jweb_contents_delegate, jobject jcontext_menu_populator) { content::ContentViewCore* content_view_core = content::ContentViewCore::GetNativeContentViewCore(env, jcontent_view_core); DCHECK(content_view_core); DCHECK(content_view_core->GetWebContents()); web_contents_.reset(content_view_core->GetWebContents()); TabHelpers::AttachTabHelpers(web_contents_.get()); SetWindowSessionID(session_window_id_.id()); session_tab_id_.set_id( SessionTabHelper::FromWebContents(web_contents())->session_id().id()); ContextMenuHelper::FromWebContents(web_contents())->SetPopulator( jcontext_menu_populator); WindowAndroidHelper::FromWebContents(web_contents())-> SetWindowAndroid(content_view_core->GetWindowAndroid()); CoreTabHelper::FromWebContents(web_contents())->set_delegate(this); web_contents_delegate_.reset( new chrome::android::ChromeWebContentsDelegateAndroid( env, jweb_contents_delegate)); web_contents_delegate_->LoadProgressChanged(web_contents(), 0); web_contents()->SetDelegate(web_contents_delegate_.get()); notification_registrar_.Add( this, chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED, content::Source(web_contents())); notification_registrar_.Add( this, chrome::NOTIFICATION_FAVICON_UPDATED, content::Source(web_contents())); notification_registrar_.Add( this, content::NOTIFICATION_NAV_ENTRY_CHANGED, content::Source( &web_contents()->GetController())); synced_tab_delegate_->SetWebContents(web_contents()); // Verify that the WebContents this tab represents matches the expected // off the record state. CHECK_EQ(GetProfile()->IsOffTheRecord(), incognito); } void TabAndroid::DestroyWebContents(JNIEnv* env, jobject obj, jboolean delete_native) { DCHECK(web_contents()); notification_registrar_.Remove( this, chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED, content::Source(web_contents())); notification_registrar_.Remove( this, chrome::NOTIFICATION_FAVICON_UPDATED, content::Source(web_contents())); web_contents()->SetDelegate(NULL); if (delete_native) { web_contents_.reset(); synced_tab_delegate_->ResetWebContents(); } else { // Release the WebContents so it does not get deleted by the scoped_ptr. ignore_result(web_contents_.release()); } } base::android::ScopedJavaLocalRef TabAndroid::GetWebContents( JNIEnv* env, jobject obj) { if (!web_contents_.get()) return base::android::ScopedJavaLocalRef(); return web_contents_->GetJavaWebContents(); } base::android::ScopedJavaLocalRef TabAndroid::GetProfileAndroid( JNIEnv* env, jobject obj) { Profile* profile = GetProfile(); if (!profile) return base::android::ScopedJavaLocalRef(); ProfileAndroid* profile_android = ProfileAndroid::FromProfile(profile); if (!profile_android) return base::android::ScopedJavaLocalRef(); return profile_android->GetJavaObject(); } TabAndroid::TabLoadStatus TabAndroid::LoadUrl(JNIEnv* env, jobject obj, jstring url, jstring j_extra_headers, jbyteArray j_post_data, jint page_transition, jstring j_referrer_url, jint referrer_policy) { content::ContentViewCore* content_view = GetContentViewCore(); if (!content_view) return PAGE_LOAD_FAILED; GURL gurl(base::android::ConvertJavaStringToUTF8(env, url)); if (gurl.is_empty()) return PAGE_LOAD_FAILED; // If the page was prerendered, use it. // Note in incognito mode, we don't have a PrerenderManager. prerender::PrerenderManager* prerender_manager = prerender::PrerenderManagerFactory::GetForProfile(GetProfile()); if (prerender_manager) { bool prefetched_page_loaded = HasPrerenderedUrl(gurl); // Getting the load status before MaybeUsePrerenderedPage() b/c it resets. chrome::NavigateParams params(NULL, web_contents()); if (prerender_manager->MaybeUsePrerenderedPage(gurl, ¶ms)) { return prefetched_page_loaded ? FULL_PRERENDERED_PAGE_LOAD : PARTIAL_PRERENDERED_PAGE_LOAD; } } GURL fixed_url(URLFixerUpper::FixupURL(gurl.possibly_invalid_spec(), std::string())); if (!fixed_url.is_valid()) return PAGE_LOAD_FAILED; if (!HandleNonNavigationAboutURL(fixed_url)) { // Notify the GoogleURLTracker of searches, it might want to change the // actual Google site used (for instance when in the UK, google.co.uk, when // in the US google.com). // Note that this needs to happen before we initiate the navigation as the // GoogleURLTracker uses the navigation pending notification to trigger the // infobar. if (google_util::IsGoogleSearchUrl(fixed_url) && (page_transition & content::PAGE_TRANSITION_GENERATED)) { GoogleURLTracker::GoogleURLSearchCommitted(GetProfile()); } // Record UMA "ShowHistory" here. That way it'll pick up both user // typing chrome://history as well as selecting from the drop down menu. if (fixed_url.spec() == chrome::kChromeUIHistoryURL) { content::RecordAction(base::UserMetricsAction("ShowHistory")); } content::NavigationController::LoadURLParams load_params(fixed_url); if (j_extra_headers) { load_params.extra_headers = base::android::ConvertJavaStringToUTF8( env, j_extra_headers); } if (j_post_data) { load_params.load_type = content::NavigationController::LOAD_TYPE_BROWSER_INITIATED_HTTP_POST; std::vector post_data; base::android::JavaByteArrayToByteVector(env, j_post_data, &post_data); load_params.browser_initiated_post_data = base::RefCountedBytes::TakeVector(&post_data); } load_params.transition_type = content::PageTransitionFromInt(page_transition); if (j_referrer_url) { load_params.referrer = content::Referrer( GURL(base::android::ConvertJavaStringToUTF8(env, j_referrer_url)), static_cast(referrer_policy)); } content_view->LoadUrl(load_params); } return DEFAULT_PAGE_LOAD; } ToolbarModel::SecurityLevel TabAndroid::GetSecurityLevel(JNIEnv* env, jobject obj) { return ToolbarModelImpl::GetSecurityLevelForWebContents(web_contents()); } void TabAndroid::SetActiveNavigationEntryTitleForUrl(JNIEnv* env, jobject obj, jstring jurl, jstring jtitle) { DCHECK(web_contents()); base::string16 title; if (jtitle) title = base::android::ConvertJavaStringToUTF16(env, jtitle); std::string url; if (jurl) url = base::android::ConvertJavaStringToUTF8(env, jurl); content::NavigationEntry* entry = web_contents()->GetController().GetVisibleEntry(); if (entry && url == entry->GetVirtualURL().spec()) entry->SetTitle(title); } bool TabAndroid::Print(JNIEnv* env, jobject obj) { if (!web_contents()) return false; printing::PrintViewManagerBasic::CreateForWebContents(web_contents()); printing::PrintViewManagerBasic* print_view_manager = printing::PrintViewManagerBasic::FromWebContents(web_contents()); if (print_view_manager == NULL) return false; print_view_manager->PrintNow(); return true; } prerender::PrerenderManager* TabAndroid::GetPrerenderManager() const { Profile* profile = GetProfile(); if (!profile) return NULL; return prerender::PrerenderManagerFactory::GetForProfile(profile); } static void Init(JNIEnv* env, jobject obj) { TRACE_EVENT0("native", "TabAndroid::Init"); // This will automatically bind to the Java object and pass ownership there. new TabAndroid(env, obj); } bool TabAndroid::RegisterTabAndroid(JNIEnv* env) { return RegisterNativesImpl(env); }