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