Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / webui / foreign_session_handler.cc
blobbed88b282a465a938fbb3d4a851cf467a05a7d53
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.
5 #include "chrome/browser/ui/webui/foreign_session_handler.h"
7 #include <algorithm>
8 #include <string>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/i18n/time_formatting.h"
14 #include "base/metrics/histogram_macros.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/time/time.h"
20 #include "base/values.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/sessions/session_restore.h"
24 #include "chrome/browser/sync/profile_sync_service.h"
25 #include "chrome/browser/sync/profile_sync_service_factory.h"
26 #include "chrome/browser/ui/host_desktop.h"
27 #include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
28 #include "chrome/common/pref_names.h"
29 #include "chrome/common/url_constants.h"
30 #include "chrome/grit/generated_resources.h"
31 #include "components/pref_registry/pref_registry_syncable.h"
32 #include "content/public/browser/notification_service.h"
33 #include "content/public/browser/notification_source.h"
34 #include "content/public/browser/url_data_source.h"
35 #include "content/public/browser/web_contents.h"
36 #include "content/public/browser/web_ui.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"
41 namespace browser_sync {
43 namespace {
45 // Maximum number of sessions we're going to display on the NTP
46 const size_t kMaxSessionsToShow = 10;
48 // Helper method to create JSON compatible objects from Session objects.
49 scoped_ptr<base::DictionaryValue> SessionTabToValue(
50 const ::sessions::SessionTab& tab) {
51 if (tab.navigations.empty())
52 return nullptr;
54 int selected_index = std::min(tab.current_navigation_index,
55 static_cast<int>(tab.navigations.size() - 1));
56 const ::sessions::SerializedNavigationEntry& current_navigation =
57 tab.navigations.at(selected_index);
58 GURL tab_url = current_navigation.virtual_url();
59 if (!tab_url.is_valid() ||
60 tab_url.spec() == chrome::kChromeUINewTabURL) {
61 return nullptr;
64 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
65 NewTabUI::SetUrlTitleAndDirection(dictionary.get(),
66 current_navigation.title(), tab_url);
67 dictionary->SetString("type", "tab");
68 dictionary->SetDouble("timestamp",
69 static_cast<double>(tab.timestamp.ToInternalValue()));
70 // TODO(jeremycho): This should probably be renamed to tabId to avoid
71 // confusion with the ID corresponding to a session. Investigate all the
72 // places (C++ and JS) where this is being used. (http://crbug.com/154865).
73 dictionary->SetInteger("sessionId", tab.tab_id.id());
74 return dictionary.Pass();
77 // Helper for initializing a boilerplate SessionWindow JSON compatible object.
78 scoped_ptr<base::DictionaryValue> BuildWindowData(
79 base::Time modification_time,
80 SessionID::id_type window_id) {
81 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
82 // The items which are to be written into |dictionary| are also described in
83 // chrome/browser/resources/ntp4/other_sessions.js in @typedef for WindowData.
84 // Please update it whenever you add or remove any keys here.
85 dictionary->SetString("type", "window");
86 dictionary->SetDouble("timestamp", modification_time.ToInternalValue());
87 const base::TimeDelta last_synced = base::Time::Now() - modification_time;
88 // If clock skew leads to a future time, or we last synced less than a minute
89 // ago, output "Just now".
90 dictionary->SetString(
91 "userVisibleTimestamp",
92 last_synced < base::TimeDelta::FromMinutes(1)
93 ? l10n_util::GetStringUTF16(IDS_SYNC_TIME_JUST_NOW)
94 : ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_ELAPSED,
95 ui::TimeFormat::LENGTH_SHORT, last_synced));
97 dictionary->SetInteger("sessionId", window_id);
98 return dictionary.Pass();
101 // Helper method to create JSON compatible objects from SessionWindow objects.
102 scoped_ptr<base::DictionaryValue> SessionWindowToValue(
103 const ::sessions::SessionWindow& window) {
104 if (window.tabs.empty())
105 return nullptr;
106 scoped_ptr<base::ListValue> tab_values(new base::ListValue());
107 // Calculate the last |modification_time| for all entries within a window.
108 base::Time modification_time = window.timestamp;
109 for (const ::sessions::SessionTab* tab : window.tabs) {
110 scoped_ptr<base::DictionaryValue> tab_value(SessionTabToValue(*tab));
111 if (tab_value.get()) {
112 modification_time = std::max(modification_time,
113 tab->timestamp);
114 tab_values->Append(tab_value.release());
117 if (tab_values->GetSize() == 0)
118 return nullptr;
119 scoped_ptr<base::DictionaryValue> dictionary(
120 BuildWindowData(window.timestamp, window.window_id.id()));
121 dictionary->Set("tabs", tab_values.release());
122 return dictionary.Pass();
125 } // namespace
127 ForeignSessionHandler::ForeignSessionHandler() {
128 load_attempt_time_ = base::TimeTicks::Now();
131 // static
132 void ForeignSessionHandler::RegisterProfilePrefs(
133 user_prefs::PrefRegistrySyncable* registry) {
134 registry->RegisterDictionaryPref(prefs::kNtpCollapsedForeignSessions);
137 // static
138 void ForeignSessionHandler::OpenForeignSessionTab(
139 content::WebUI* web_ui,
140 const std::string& session_string_value,
141 SessionID::id_type window_num,
142 SessionID::id_type tab_id,
143 const WindowOpenDisposition& disposition) {
144 sync_driver::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui);
145 if (!open_tabs)
146 return;
148 // We don't actually care about |window_num|, this is just a sanity check.
149 DCHECK_LT(kInvalidId, window_num);
150 const ::sessions::SessionTab* tab;
151 if (!open_tabs->GetForeignTab(session_string_value, tab_id, &tab)) {
152 LOG(ERROR) << "Failed to load foreign tab.";
153 return;
155 if (tab->navigations.empty()) {
156 LOG(ERROR) << "Foreign tab no longer has valid navigations.";
157 return;
159 SessionRestore::RestoreForeignSessionTab(
160 web_ui->GetWebContents(), *tab, disposition);
163 // static
164 void ForeignSessionHandler::OpenForeignSessionWindows(
165 content::WebUI* web_ui,
166 const std::string& session_string_value,
167 SessionID::id_type window_num) {
168 sync_driver::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui);
169 if (!open_tabs)
170 return;
172 std::vector<const ::sessions::SessionWindow*> windows;
173 // Note: we don't own the ForeignSessions themselves.
174 if (!open_tabs->GetForeignSession(session_string_value, &windows)) {
175 LOG(ERROR) << "ForeignSessionHandler failed to get session data from"
176 "OpenTabsUIDelegate.";
177 return;
179 std::vector<const ::sessions::SessionWindow*>::const_iterator iter_begin =
180 windows.begin() + (window_num == kInvalidId ? 0 : window_num);
181 std::vector<const ::sessions::SessionWindow*>::const_iterator iter_end =
182 window_num == kInvalidId ?
183 std::vector<const ::sessions::SessionWindow*>::const_iterator(
184 windows.end()) : iter_begin + 1;
185 chrome::HostDesktopType host_desktop_type =
186 chrome::GetHostDesktopTypeForNativeView(
187 web_ui->GetWebContents()->GetNativeView());
188 SessionRestore::RestoreForeignSessionWindows(
189 Profile::FromWebUI(web_ui), host_desktop_type, iter_begin, iter_end);
192 // static
193 sync_driver::OpenTabsUIDelegate* ForeignSessionHandler::GetOpenTabsUIDelegate(
194 content::WebUI* web_ui) {
195 Profile* profile = Profile::FromWebUI(web_ui);
196 ProfileSyncService* service =
197 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
199 // Only return the delegate if it exists and it is done syncing sessions.
200 if (service && service->IsSyncActive())
201 return service->GetOpenTabsUIDelegate();
203 return NULL;
206 void ForeignSessionHandler::RegisterMessages() {
207 Profile* profile = Profile::FromWebUI(web_ui());
208 ProfileSyncService* service =
209 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
210 registrar_.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
211 content::Source<ProfileSyncService>(service));
212 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
213 content::Source<Profile>(profile));
214 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED,
215 content::Source<Profile>(profile));
217 web_ui()->RegisterMessageCallback("deleteForeignSession",
218 base::Bind(&ForeignSessionHandler::HandleDeleteForeignSession,
219 base::Unretained(this)));
220 web_ui()->RegisterMessageCallback("getForeignSessions",
221 base::Bind(&ForeignSessionHandler::HandleGetForeignSessions,
222 base::Unretained(this)));
223 web_ui()->RegisterMessageCallback("openForeignSession",
224 base::Bind(&ForeignSessionHandler::HandleOpenForeignSession,
225 base::Unretained(this)));
226 web_ui()->RegisterMessageCallback("setForeignSessionCollapsed",
227 base::Bind(&ForeignSessionHandler::HandleSetForeignSessionCollapsed,
228 base::Unretained(this)));
231 void ForeignSessionHandler::Observe(
232 int type,
233 const content::NotificationSource& source,
234 const content::NotificationDetails& details) {
235 switch (type) {
236 case chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED:
237 // Tab sync is disabled, so clean up data about collapsed sessions.
238 Profile::FromWebUI(web_ui())->GetPrefs()->ClearPref(
239 prefs::kNtpCollapsedForeignSessions);
240 // Fall through.
241 case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE:
242 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
243 HandleGetForeignSessions(nullptr);
244 break;
245 default:
246 NOTREACHED();
250 bool ForeignSessionHandler::IsTabSyncEnabled() {
251 Profile* profile = Profile::FromWebUI(web_ui());
252 ProfileSyncService* service =
253 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
254 return service && service->GetActiveDataTypes().Has(syncer::PROXY_TABS);
257 base::string16 ForeignSessionHandler::FormatSessionTime(
258 const base::Time& time) {
259 // Return a time like "1 hour ago", "2 days ago", etc.
260 base::Time now = base::Time::Now();
261 // TimeFormat does not support negative TimeDelta values, so then we use 0.
262 return ui::TimeFormat::Simple(
263 ui::TimeFormat::FORMAT_ELAPSED, ui::TimeFormat::LENGTH_SHORT,
264 now < time ? base::TimeDelta() : now - time);
267 void ForeignSessionHandler::HandleGetForeignSessions(
268 const base::ListValue* /*args*/) {
269 sync_driver::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui());
270 std::vector<const sync_driver::SyncedSession*> sessions;
272 base::ListValue session_list;
273 if (open_tabs && open_tabs->GetAllForeignSessions(&sessions)) {
274 if (!load_attempt_time_.is_null()) {
275 UMA_HISTOGRAM_TIMES("Sync.SessionsRefreshDelay",
276 base::TimeTicks::Now() - load_attempt_time_);
277 load_attempt_time_ = base::TimeTicks();
280 // Use a pref to keep track of sessions that were collapsed by the user.
281 // To prevent the pref from accumulating stale sessions, clear it each time
282 // and only add back sessions that are still current.
283 DictionaryPrefUpdate pref_update(Profile::FromWebUI(web_ui())->GetPrefs(),
284 prefs::kNtpCollapsedForeignSessions);
285 base::DictionaryValue* current_collapsed_sessions = pref_update.Get();
286 scoped_ptr<base::DictionaryValue> collapsed_sessions(
287 current_collapsed_sessions->DeepCopy());
288 current_collapsed_sessions->Clear();
290 // Note: we don't own the SyncedSessions themselves.
291 for (size_t i = 0; i < sessions.size() && i < kMaxSessionsToShow; ++i) {
292 const sync_driver::SyncedSession* session = sessions[i];
293 const std::string& session_tag = session->session_tag;
294 scoped_ptr<base::DictionaryValue> session_data(
295 new base::DictionaryValue());
296 // The items which are to be written into |session_data| are also
297 // described in chrome/browser/resources/ntp4/other_sessions.js in
298 // @typedef for SessionData. Please update it whenever you add or remove
299 // any keys here.
300 session_data->SetString("tag", session_tag);
301 session_data->SetString("name", session->session_name);
302 session_data->SetString("deviceType", session->DeviceTypeAsString());
303 session_data->SetString("modifiedTime",
304 FormatSessionTime(session->modified_time));
306 bool is_collapsed = collapsed_sessions->HasKey(session_tag);
307 session_data->SetBoolean("collapsed", is_collapsed);
308 if (is_collapsed)
309 current_collapsed_sessions->SetBoolean(session_tag, true);
311 scoped_ptr<base::ListValue> window_list(new base::ListValue());
312 const std::string group_name =
313 base::FieldTrialList::FindFullName("TabSyncByRecency");
314 if (group_name != "Enabled") {
315 // Order tabs by visual order within window.
316 for (auto map_iter : session->windows) {
317 scoped_ptr<base::DictionaryValue> window_data(
318 SessionWindowToValue(*map_iter.second));
319 if (window_data.get())
320 window_list->Append(window_data.release());
322 } else {
323 // Order tabs by recency. This involves creating a synthetic singleton
324 // window that contains all the tabs of the session.
325 base::Time modification_time;
326 std::vector<const ::sessions::SessionTab*> tabs;
327 open_tabs->GetForeignSessionTabs(session_tag, &tabs);
328 scoped_ptr<base::ListValue> tab_values(new base::ListValue());
329 for (const ::sessions::SessionTab* tab : tabs) {
330 scoped_ptr<base::DictionaryValue> tab_value(SessionTabToValue(*tab));
331 if (tab_value.get()) {
332 modification_time = std::max(modification_time, tab->timestamp);
333 tab_values->Append(tab_value.release());
336 if (tab_values->GetSize() != 0) {
337 scoped_ptr<base::DictionaryValue> window_data(
338 BuildWindowData(modification_time, 1));
339 window_data->Set("tabs", tab_values.release());
340 window_list->Append(window_data.release());
344 session_data->Set("windows", window_list.release());
345 session_list.Append(session_data.release());
348 base::FundamentalValue tab_sync_enabled(IsTabSyncEnabled());
349 web_ui()->CallJavascriptFunction("setForeignSessions", session_list,
350 tab_sync_enabled);
353 void ForeignSessionHandler::HandleOpenForeignSession(
354 const base::ListValue* args) {
355 size_t num_args = args->GetSize();
356 // Expect either 1 or 8 args. For restoring an entire session, only
357 // one argument is required -- the session tag. To restore a tab,
358 // the additional args required are the window id, the tab id,
359 // and 4 properties of the event object (button, altKey, ctrlKey,
360 // metaKey, shiftKey) for determining how to open the tab.
361 if (num_args != 8U && num_args != 1U) {
362 LOG(ERROR) << "openForeignSession called with " << args->GetSize()
363 << " arguments.";
364 return;
367 // Extract the session tag (always provided).
368 std::string session_string_value;
369 if (!args->GetString(0, &session_string_value)) {
370 LOG(ERROR) << "Failed to extract session tag.";
371 return;
374 // Extract window number.
375 std::string window_num_str;
376 int window_num = kInvalidId;
377 if (num_args >= 2 && (!args->GetString(1, &window_num_str) ||
378 !base::StringToInt(window_num_str, &window_num))) {
379 LOG(ERROR) << "Failed to extract window number.";
380 return;
383 // Extract tab id.
384 std::string tab_id_str;
385 SessionID::id_type tab_id = kInvalidId;
386 if (num_args >= 3 && (!args->GetString(2, &tab_id_str) ||
387 !base::StringToInt(tab_id_str, &tab_id))) {
388 LOG(ERROR) << "Failed to extract tab SessionID.";
389 return;
392 if (tab_id != kInvalidId) {
393 WindowOpenDisposition disposition = webui::GetDispositionFromClick(args, 3);
394 OpenForeignSessionTab(
395 web_ui(), session_string_value, window_num, tab_id, disposition);
396 } else {
397 OpenForeignSessionWindows(web_ui(), session_string_value, window_num);
401 void ForeignSessionHandler::HandleDeleteForeignSession(
402 const base::ListValue* args) {
403 if (args->GetSize() != 1U) {
404 LOG(ERROR) << "Wrong number of args to deleteForeignSession";
405 return;
408 // Get the session tag argument (required).
409 std::string session_tag;
410 if (!args->GetString(0, &session_tag)) {
411 LOG(ERROR) << "Unable to extract session tag";
412 return;
415 sync_driver::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(web_ui());
416 if (open_tabs)
417 open_tabs->DeleteForeignSession(session_tag);
420 void ForeignSessionHandler::HandleSetForeignSessionCollapsed(
421 const base::ListValue* args) {
422 if (args->GetSize() != 2U) {
423 LOG(ERROR) << "Wrong number of args to setForeignSessionCollapsed";
424 return;
427 // Get the session tag argument (required).
428 std::string session_tag;
429 if (!args->GetString(0, &session_tag)) {
430 LOG(ERROR) << "Unable to extract session tag";
431 return;
434 bool is_collapsed;
435 if (!args->GetBoolean(1, &is_collapsed)) {
436 LOG(ERROR) << "Unable to extract boolean argument";
437 return;
440 // Store session tags for collapsed sessions in a preference so that the
441 // collapsed state persists.
442 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
443 DictionaryPrefUpdate update(prefs, prefs::kNtpCollapsedForeignSessions);
444 if (is_collapsed)
445 update.Get()->SetBoolean(session_tag, true);
446 else
447 update.Get()->Remove(session_tag, NULL);
450 } // namespace browser_sync