Home | History | Annotate | Download | only in ntp
      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/ui/webui/ntp/foreign_session_handler.h"
      6 
      7 #include <algorithm>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/bind.h"
     12 #include "base/bind_helpers.h"
     13 #include "base/i18n/time_formatting.h"
     14 #include "base/memory/scoped_vector.h"
     15 #include "base/prefs/pref_service.h"
     16 #include "base/prefs/scoped_user_pref_update.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/utf_string_conversions.h"
     19 #include "base/values.h"
     20 #include "chrome/browser/chrome_notification_types.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/browser/sessions/session_restore.h"
     23 #include "chrome/browser/sync/profile_sync_service.h"
     24 #include "chrome/browser/sync/profile_sync_service_factory.h"
     25 #include "chrome/browser/ui/host_desktop.h"
     26 #include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
     27 #include "chrome/common/pref_names.h"
     28 #include "chrome/common/url_constants.h"
     29 #include "components/user_prefs/pref_registry_syncable.h"
     30 #include "content/public/browser/notification_service.h"
     31 #include "content/public/browser/notification_source.h"
     32 #include "content/public/browser/url_data_source.h"
     33 #include "content/public/browser/web_contents.h"
     34 #include "content/public/browser/web_contents_view.h"
     35 #include "content/public/browser/web_ui.h"
     36 #include "grit/generated_resources.h"
     37 #include "ui/base/l10n/l10n_util.h"
     38 #include "ui/base/l10n/time_format.h"
     39 #include "ui/base/webui/web_ui_util.h"
     40 
     41 namespace browser_sync {
     42 
     43 // Maximum number of sessions we're going to display on the NTP
     44 static const size_t kMaxSessionsToShow = 10;
     45 
     46 namespace {
     47 
     48 // Comparator function for use with std::sort that will sort sessions by
     49 // descending modified_time (i.e., most recent first).
     50 bool SortSessionsByRecency(const SyncedSession* s1, const SyncedSession* s2) {
     51   return s1->modified_time > s2->modified_time;
     52 }
     53 
     54 }  // namepace
     55 
     56 ForeignSessionHandler::ForeignSessionHandler() {
     57 }
     58 
     59 // static
     60 void ForeignSessionHandler::RegisterProfilePrefs(
     61     user_prefs::PrefRegistrySyncable* registry) {
     62   registry->RegisterDictionaryPref(
     63       prefs::kNtpCollapsedForeignSessions,
     64       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
     65 }
     66 
     67 // static
     68 void ForeignSessionHandler::OpenForeignSessionTab(
     69     content::WebUI* web_ui,
     70     const std::string& session_string_value,
     71     SessionID::id_type window_num,
     72     SessionID::id_type tab_id,
     73     const WindowOpenDisposition& disposition) {
     74   OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui);
     75   if (!open_tabs)
     76     return;
     77 
     78   // We don't actually care about |window_num|, this is just a sanity check.
     79   DCHECK_LT(kInvalidId, window_num);
     80   const SessionTab* tab;
     81   if (!open_tabs->GetForeignTab(session_string_value, tab_id, &tab)) {
     82     LOG(ERROR) << "Failed to load foreign tab.";
     83     return;
     84   }
     85   if (tab->navigations.empty()) {
     86     LOG(ERROR) << "Foreign tab no longer has valid navigations.";
     87     return;
     88   }
     89   SessionRestore::RestoreForeignSessionTab(
     90       web_ui->GetWebContents(), *tab, disposition);
     91 }
     92 
     93 // static
     94 void ForeignSessionHandler::OpenForeignSessionWindows(
     95     content::WebUI* web_ui,
     96     const std::string& session_string_value,
     97     SessionID::id_type window_num) {
     98   OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui);
     99   if (!open_tabs)
    100     return;
    101 
    102   std::vector<const SessionWindow*> windows;
    103   // Note: we don't own the ForeignSessions themselves.
    104   if (!open_tabs->GetForeignSession(session_string_value, &windows)) {
    105     LOG(ERROR) << "ForeignSessionHandler failed to get session data from"
    106         "OpenTabsUIDelegate.";
    107     return;
    108   }
    109   std::vector<const SessionWindow*>::const_iterator iter_begin =
    110       windows.begin() + (window_num == kInvalidId ? 0 : window_num);
    111   std::vector<const SessionWindow*>::const_iterator iter_end =
    112       window_num == kInvalidId ?
    113       std::vector<const SessionWindow*>::const_iterator(windows.end()) :
    114       iter_begin + 1;
    115   chrome::HostDesktopType host_desktop_type =
    116       chrome::GetHostDesktopTypeForNativeView(
    117           web_ui->GetWebContents()->GetView()->GetNativeView());
    118   SessionRestore::RestoreForeignSessionWindows(
    119       Profile::FromWebUI(web_ui), host_desktop_type, iter_begin, iter_end);
    120 }
    121 
    122 // static
    123 bool ForeignSessionHandler::SessionTabToValue(
    124     const SessionTab& tab,
    125     DictionaryValue* dictionary) {
    126   if (tab.navigations.empty())
    127     return false;
    128 
    129   int selected_index = std::min(tab.current_navigation_index,
    130                                 static_cast<int>(tab.navigations.size() - 1));
    131   const ::sessions::SerializedNavigationEntry& current_navigation =
    132       tab.navigations.at(selected_index);
    133   GURL tab_url = current_navigation.virtual_url();
    134   if (tab_url == GURL(chrome::kChromeUINewTabURL))
    135     return false;
    136 
    137   NewTabUI::SetUrlTitleAndDirection(dictionary, current_navigation.title(),
    138                                     tab_url);
    139   dictionary->SetString("type", "tab");
    140   dictionary->SetDouble("timestamp",
    141                         static_cast<double>(tab.timestamp.ToInternalValue()));
    142   // TODO(jeremycho): This should probably be renamed to tabId to avoid
    143   // confusion with the ID corresponding to a session.  Investigate all the
    144   // places (C++ and JS) where this is being used.  (http://crbug.com/154865).
    145   dictionary->SetInteger("sessionId", tab.tab_id.id());
    146   return true;
    147 }
    148 
    149 // static
    150 OpenTabsUIDelegate* ForeignSessionHandler::GetOpenTabsUIDelegate(
    151     content::WebUI* web_ui) {
    152   Profile* profile = Profile::FromWebUI(web_ui);
    153   ProfileSyncService* service =
    154       ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
    155 
    156   // Only return the delegate if it exists and it is done syncing sessions.
    157   if (service && service->ShouldPushChanges())
    158     return service->GetOpenTabsUIDelegate();
    159 
    160   return NULL;
    161 }
    162 
    163 void ForeignSessionHandler::RegisterMessages() {
    164   Init();
    165   web_ui()->RegisterMessageCallback("deleteForeignSession",
    166       base::Bind(&ForeignSessionHandler::HandleDeleteForeignSession,
    167                  base::Unretained(this)));
    168   web_ui()->RegisterMessageCallback("getForeignSessions",
    169       base::Bind(&ForeignSessionHandler::HandleGetForeignSessions,
    170                  base::Unretained(this)));
    171   web_ui()->RegisterMessageCallback("openForeignSession",
    172       base::Bind(&ForeignSessionHandler::HandleOpenForeignSession,
    173                  base::Unretained(this)));
    174   web_ui()->RegisterMessageCallback("setForeignSessionCollapsed",
    175       base::Bind(&ForeignSessionHandler::HandleSetForeignSessionCollapsed,
    176                  base::Unretained(this)));
    177 }
    178 
    179 void ForeignSessionHandler::Init() {
    180   Profile* profile = Profile::FromWebUI(web_ui());
    181   ProfileSyncService* service =
    182       ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
    183   registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
    184                  content::Source<ProfileSyncService>(service));
    185   registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
    186                  content::Source<Profile>(profile));
    187   registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED,
    188                  content::Source<Profile>(profile));
    189 }
    190 
    191 void ForeignSessionHandler::Observe(
    192     int type,
    193     const content::NotificationSource& source,
    194     const content::NotificationDetails& details) {
    195   ListValue list_value;
    196 
    197   switch (type) {
    198     case chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED:
    199       // Tab sync is disabled, so clean up data about collapsed sessions.
    200       Profile::FromWebUI(web_ui())->GetPrefs()->ClearPref(
    201           prefs::kNtpCollapsedForeignSessions);
    202       // Fall through.
    203     case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE:
    204     case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
    205       HandleGetForeignSessions(&list_value);
    206       break;
    207     default:
    208       NOTREACHED();
    209   }
    210 }
    211 
    212 
    213 bool ForeignSessionHandler::IsTabSyncEnabled() {
    214   Profile* profile = Profile::FromWebUI(web_ui());
    215   ProfileSyncService* service =
    216       ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
    217   return service && service->GetActiveDataTypes().Has(syncer::PROXY_TABS);
    218 }
    219 
    220 base::string16 ForeignSessionHandler::FormatSessionTime(
    221     const base::Time& time) {
    222   // Return a time like "1 hour ago", "2 days ago", etc.
    223   base::Time now = base::Time::Now();
    224   // TimeElapsed does not support negative TimeDelta values, so then we use 0.
    225   return ui::TimeFormat::TimeElapsed(
    226       now < time ? base::TimeDelta() : now - time);
    227 }
    228 
    229 void ForeignSessionHandler::HandleGetForeignSessions(const ListValue* args) {
    230   OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui());
    231   std::vector<const SyncedSession*> sessions;
    232 
    233   ListValue session_list;
    234   if (open_tabs && open_tabs->GetAllForeignSessions(&sessions)) {
    235     // Sort sessions from most recent to least recent.
    236     std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
    237 
    238     // Use a pref to keep track of sessions that were collapsed by the user.
    239     // To prevent the pref from accumulating stale sessions, clear it each time
    240     // and only add back sessions that are still current.
    241     DictionaryPrefUpdate pref_update(Profile::FromWebUI(web_ui())->GetPrefs(),
    242                                      prefs::kNtpCollapsedForeignSessions);
    243     DictionaryValue* current_collapsed_sessions = pref_update.Get();
    244     scoped_ptr<DictionaryValue> collapsed_sessions(
    245         current_collapsed_sessions->DeepCopy());
    246     current_collapsed_sessions->Clear();
    247 
    248     // Note: we don't own the SyncedSessions themselves.
    249     for (size_t i = 0; i < sessions.size() && i < kMaxSessionsToShow; ++i) {
    250       const SyncedSession* session = sessions[i];
    251       const std::string& session_tag = session->session_tag;
    252       scoped_ptr<DictionaryValue> session_data(new DictionaryValue());
    253       session_data->SetString("tag", session_tag);
    254       session_data->SetString("name", session->session_name);
    255       session_data->SetString("deviceType", session->DeviceTypeAsString());
    256       session_data->SetString("modifiedTime",
    257                               FormatSessionTime(session->modified_time));
    258 
    259       bool is_collapsed = collapsed_sessions->HasKey(session_tag);
    260       session_data->SetBoolean("collapsed", is_collapsed);
    261       if (is_collapsed)
    262         current_collapsed_sessions->SetBoolean(session_tag, true);
    263 
    264       scoped_ptr<ListValue> window_list(new ListValue());
    265       for (SyncedSession::SyncedWindowMap::const_iterator it =
    266            session->windows.begin(); it != session->windows.end(); ++it) {
    267         SessionWindow* window = it->second;
    268         scoped_ptr<DictionaryValue> window_data(new DictionaryValue());
    269         if (SessionWindowToValue(*window, window_data.get()))
    270           window_list->Append(window_data.release());
    271       }
    272 
    273       session_data->Set("windows", window_list.release());
    274       session_list.Append(session_data.release());
    275     }
    276   }
    277   base::FundamentalValue tab_sync_enabled(IsTabSyncEnabled());
    278   web_ui()->CallJavascriptFunction("ntp.setForeignSessions",
    279                                    session_list,
    280                                    tab_sync_enabled);
    281 }
    282 
    283 void ForeignSessionHandler::HandleOpenForeignSession(const ListValue* args) {
    284   size_t num_args = args->GetSize();
    285   // Expect either 1 or 8 args. For restoring an entire session, only
    286   // one argument is required -- the session tag. To restore a tab,
    287   // the additional args required are the window id, the tab id,
    288   // and 4 properties of the event object (button, altKey, ctrlKey,
    289   // metaKey, shiftKey) for determining how to open the tab.
    290   if (num_args != 8U && num_args != 1U) {
    291     LOG(ERROR) << "openForeignSession called with " << args->GetSize()
    292                << " arguments.";
    293     return;
    294   }
    295 
    296   // Extract the session tag (always provided).
    297   std::string session_string_value;
    298   if (!args->GetString(0, &session_string_value)) {
    299     LOG(ERROR) << "Failed to extract session tag.";
    300     return;
    301   }
    302 
    303   // Extract window number.
    304   std::string window_num_str;
    305   int window_num = kInvalidId;
    306   if (num_args >= 2 && (!args->GetString(1, &window_num_str) ||
    307       !base::StringToInt(window_num_str, &window_num))) {
    308     LOG(ERROR) << "Failed to extract window number.";
    309     return;
    310   }
    311 
    312   // Extract tab id.
    313   std::string tab_id_str;
    314   SessionID::id_type tab_id = kInvalidId;
    315   if (num_args >= 3 && (!args->GetString(2, &tab_id_str) ||
    316       !base::StringToInt(tab_id_str, &tab_id))) {
    317     LOG(ERROR) << "Failed to extract tab SessionID.";
    318     return;
    319   }
    320 
    321   if (tab_id != kInvalidId) {
    322     WindowOpenDisposition disposition = webui::GetDispositionFromClick(args, 3);
    323     OpenForeignSessionTab(
    324         web_ui(), session_string_value, window_num, tab_id, disposition);
    325   } else {
    326     OpenForeignSessionWindows(web_ui(), session_string_value, window_num);
    327   }
    328 }
    329 
    330 void ForeignSessionHandler::HandleDeleteForeignSession(const ListValue* args) {
    331   if (args->GetSize() != 1U) {
    332     LOG(ERROR) << "Wrong number of args to deleteForeignSession";
    333     return;
    334   }
    335 
    336   // Get the session tag argument (required).
    337   std::string session_tag;
    338   if (!args->GetString(0, &session_tag)) {
    339     LOG(ERROR) << "Unable to extract session tag";
    340     return;
    341   }
    342 
    343   OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui());
    344   if (open_tabs)
    345     open_tabs->DeleteForeignSession(session_tag);
    346 }
    347 
    348 void ForeignSessionHandler::HandleSetForeignSessionCollapsed(
    349     const ListValue* args) {
    350   if (args->GetSize() != 2U) {
    351     LOG(ERROR) << "Wrong number of args to setForeignSessionCollapsed";
    352     return;
    353   }
    354 
    355   // Get the session tag argument (required).
    356   std::string session_tag;
    357   if (!args->GetString(0, &session_tag)) {
    358     LOG(ERROR) << "Unable to extract session tag";
    359     return;
    360   }
    361 
    362   bool is_collapsed;
    363   if (!args->GetBoolean(1, &is_collapsed)) {
    364     LOG(ERROR) << "Unable to extract boolean argument";
    365     return;
    366   }
    367 
    368   // Store session tags for collapsed sessions in a preference so that the
    369   // collapsed state persists.
    370   PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
    371   DictionaryPrefUpdate update(prefs, prefs::kNtpCollapsedForeignSessions);
    372   if (is_collapsed)
    373     update.Get()->SetBoolean(session_tag, true);
    374   else
    375     update.Get()->Remove(session_tag, NULL);
    376 }
    377 
    378 bool ForeignSessionHandler::SessionWindowToValue(
    379     const SessionWindow& window,
    380     DictionaryValue* dictionary) {
    381   if (window.tabs.empty()) {
    382     NOTREACHED();
    383     return false;
    384   }
    385   scoped_ptr<ListValue> tab_values(new ListValue());
    386   // Calculate the last |modification_time| for all entries within a window.
    387   base::Time modification_time = window.timestamp;
    388   for (size_t i = 0; i < window.tabs.size(); ++i) {
    389     scoped_ptr<DictionaryValue> tab_value(new DictionaryValue());
    390     if (SessionTabToValue(*window.tabs[i], tab_value.get())) {
    391       modification_time = std::max(modification_time,
    392                                    window.tabs[i]->timestamp);
    393       tab_values->Append(tab_value.release());
    394     }
    395   }
    396   if (tab_values->GetSize() == 0)
    397     return false;
    398   dictionary->SetString("type", "window");
    399   dictionary->SetDouble("timestamp", modification_time.ToInternalValue());
    400   const base::TimeDelta last_synced = base::Time::Now() - modification_time;
    401   // If clock skew leads to a future time, or we last synced less than a minute
    402   // ago, output "Just now".
    403   dictionary->SetString("userVisibleTimestamp",
    404       last_synced < base::TimeDelta::FromMinutes(1) ?
    405           l10n_util::GetStringUTF16(IDS_SYNC_TIME_JUST_NOW) :
    406           ui::TimeFormat::TimeElapsed(last_synced));
    407   dictionary->SetInteger("sessionId", window.window_id.id());
    408   dictionary->Set("tabs", tab_values.release());
    409   return true;
    410 }
    411 
    412 }  // namespace browser_sync
    413