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