Home | History | Annotate | Download | only in android
      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, &params))
    409           return FULL_PRERENDERED_PAGE_LOAD;
    410       }
    411       prerenderer->Cancel();
    412     }
    413     if (prerender_manager->MaybeUsePrerenderedPage(gurl, &params)) {
    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