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"
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"
40 namespace browser_sync
{
42 // Maximum number of sessions we're going to display on the NTP
43 static const size_t kMaxSessionsToShow
= 10;
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
;
55 ForeignSessionHandler::ForeignSessionHandler() {
59 void ForeignSessionHandler::RegisterProfilePrefs(
60 user_prefs::PrefRegistrySyncable
* registry
) {
61 registry
->RegisterDictionaryPref(prefs::kNtpCollapsedForeignSessions
);
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
);
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.";
82 if (tab
->navigations
.empty()) {
83 LOG(ERROR
) << "Foreign tab no longer has valid navigations.";
86 SessionRestore::RestoreForeignSessionTab(
87 web_ui
->GetWebContents(), *tab
, disposition
);
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
);
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.";
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
);
120 bool ForeignSessionHandler::SessionTabToValue(
121 const ::sessions::SessionTab
& tab
,
122 base::DictionaryValue
* dictionary
) {
123 if (tab
.navigations
.empty())
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
))
134 NewTabUI::SetUrlTitleAndDirection(dictionary
, current_navigation
.title(),
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());
147 OpenTabsUIDelegate
* ForeignSessionHandler::GetOpenTabsUIDelegate(
148 content::WebUI
* web_ui
) {
149 Profile
* profile
= Profile::FromWebUI(web_ui
);
150 ProfileSyncService
* service
=
151 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
153 // Only return the delegate if it exists and it is done syncing sessions.
154 if (service
&& service
->SyncActive())
155 return service
->GetOpenTabsUIDelegate();
160 void ForeignSessionHandler::RegisterMessages() {
161 Profile
* profile
= Profile::FromWebUI(web_ui());
162 ProfileSyncService
* service
=
163 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
164 registrar_
.Add(this, chrome::NOTIFICATION_SYNC_CONFIGURE_DONE
,
165 content::Source
<ProfileSyncService
>(service
));
166 registrar_
.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
,
167 content::Source
<Profile
>(profile
));
168 registrar_
.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED
,
169 content::Source
<Profile
>(profile
));
171 web_ui()->RegisterMessageCallback("deleteForeignSession",
172 base::Bind(&ForeignSessionHandler::HandleDeleteForeignSession
,
173 base::Unretained(this)));
174 web_ui()->RegisterMessageCallback("getForeignSessions",
175 base::Bind(&ForeignSessionHandler::HandleGetForeignSessions
,
176 base::Unretained(this)));
177 web_ui()->RegisterMessageCallback("openForeignSession",
178 base::Bind(&ForeignSessionHandler::HandleOpenForeignSession
,
179 base::Unretained(this)));
180 web_ui()->RegisterMessageCallback("setForeignSessionCollapsed",
181 base::Bind(&ForeignSessionHandler::HandleSetForeignSessionCollapsed
,
182 base::Unretained(this)));
185 void ForeignSessionHandler::Observe(
187 const content::NotificationSource
& source
,
188 const content::NotificationDetails
& details
) {
190 case chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED
:
191 // Tab sync is disabled, so clean up data about collapsed sessions.
192 Profile::FromWebUI(web_ui())->GetPrefs()->ClearPref(
193 prefs::kNtpCollapsedForeignSessions
);
195 case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE
:
196 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
:
197 HandleGetForeignSessions(nullptr);
204 bool ForeignSessionHandler::IsTabSyncEnabled() {
205 Profile
* profile
= Profile::FromWebUI(web_ui());
206 ProfileSyncService
* service
=
207 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
208 return service
&& service
->GetActiveDataTypes().Has(syncer::PROXY_TABS
);
211 base::string16
ForeignSessionHandler::FormatSessionTime(
212 const base::Time
& time
) {
213 // Return a time like "1 hour ago", "2 days ago", etc.
214 base::Time now
= base::Time::Now();
215 // TimeFormat does not support negative TimeDelta values, so then we use 0.
216 return ui::TimeFormat::Simple(
217 ui::TimeFormat::FORMAT_ELAPSED
, ui::TimeFormat::LENGTH_SHORT
,
218 now
< time
? base::TimeDelta() : now
- time
);
221 void ForeignSessionHandler::HandleGetForeignSessions(
222 const base::ListValue
* /*args*/) {
223 OpenTabsUIDelegate
* open_tabs
= GetOpenTabsUIDelegate(web_ui());
224 std::vector
<const SyncedSession
*> sessions
;
226 base::ListValue session_list
;
227 if (open_tabs
&& open_tabs
->GetAllForeignSessions(&sessions
)) {
228 // Sort sessions from most recent to least recent.
229 std::sort(sessions
.begin(), sessions
.end(), SortSessionsByRecency
);
231 // Use a pref to keep track of sessions that were collapsed by the user.
232 // To prevent the pref from accumulating stale sessions, clear it each time
233 // and only add back sessions that are still current.
234 DictionaryPrefUpdate
pref_update(Profile::FromWebUI(web_ui())->GetPrefs(),
235 prefs::kNtpCollapsedForeignSessions
);
236 base::DictionaryValue
* current_collapsed_sessions
= pref_update
.Get();
237 scoped_ptr
<base::DictionaryValue
> collapsed_sessions(
238 current_collapsed_sessions
->DeepCopy());
239 current_collapsed_sessions
->Clear();
241 // Note: we don't own the SyncedSessions themselves.
242 for (size_t i
= 0; i
< sessions
.size() && i
< kMaxSessionsToShow
; ++i
) {
243 const SyncedSession
* session
= sessions
[i
];
244 const std::string
& session_tag
= session
->session_tag
;
245 scoped_ptr
<base::DictionaryValue
> session_data(
246 new base::DictionaryValue());
247 // The items which are to be written into |session_data| are also
248 // described in chrome/browser/resources/ntp4/other_sessions.js in
249 // @typedef for SessionData. Please update it whenever you add or remove
251 session_data
->SetString("tag", session_tag
);
252 session_data
->SetString("name", session
->session_name
);
253 session_data
->SetString("deviceType", session
->DeviceTypeAsString());
254 session_data
->SetString("modifiedTime",
255 FormatSessionTime(session
->modified_time
));
257 bool is_collapsed
= collapsed_sessions
->HasKey(session_tag
);
258 session_data
->SetBoolean("collapsed", is_collapsed
);
260 current_collapsed_sessions
->SetBoolean(session_tag
, true);
262 scoped_ptr
<base::ListValue
> window_list(new base::ListValue());
263 for (SyncedSession::SyncedWindowMap::const_iterator it
=
264 session
->windows
.begin(); it
!= session
->windows
.end(); ++it
) {
265 ::sessions::SessionWindow
* window
= it
->second
;
266 scoped_ptr
<base::DictionaryValue
> window_data(
267 new base::DictionaryValue());
268 if (SessionWindowToValue(*window
, window_data
.get()))
269 window_list
->Append(window_data
.release());
272 session_data
->Set("windows", window_list
.release());
273 session_list
.Append(session_data
.release());
276 base::FundamentalValue
tab_sync_enabled(IsTabSyncEnabled());
277 web_ui()->CallJavascriptFunction("setForeignSessions", session_list
,
281 void ForeignSessionHandler::HandleOpenForeignSession(
282 const base::ListValue
* args
) {
283 size_t num_args
= args
->GetSize();
284 // Expect either 1 or 8 args. For restoring an entire session, only
285 // one argument is required -- the session tag. To restore a tab,
286 // the additional args required are the window id, the tab id,
287 // and 4 properties of the event object (button, altKey, ctrlKey,
288 // metaKey, shiftKey) for determining how to open the tab.
289 if (num_args
!= 8U && num_args
!= 1U) {
290 LOG(ERROR
) << "openForeignSession called with " << args
->GetSize()
295 // Extract the session tag (always provided).
296 std::string session_string_value
;
297 if (!args
->GetString(0, &session_string_value
)) {
298 LOG(ERROR
) << "Failed to extract session tag.";
302 // Extract window number.
303 std::string window_num_str
;
304 int window_num
= kInvalidId
;
305 if (num_args
>= 2 && (!args
->GetString(1, &window_num_str
) ||
306 !base::StringToInt(window_num_str
, &window_num
))) {
307 LOG(ERROR
) << "Failed to extract window number.";
312 std::string tab_id_str
;
313 SessionID::id_type tab_id
= kInvalidId
;
314 if (num_args
>= 3 && (!args
->GetString(2, &tab_id_str
) ||
315 !base::StringToInt(tab_id_str
, &tab_id
))) {
316 LOG(ERROR
) << "Failed to extract tab SessionID.";
320 if (tab_id
!= kInvalidId
) {
321 WindowOpenDisposition disposition
= webui::GetDispositionFromClick(args
, 3);
322 OpenForeignSessionTab(
323 web_ui(), session_string_value
, window_num
, tab_id
, disposition
);
325 OpenForeignSessionWindows(web_ui(), session_string_value
, window_num
);
329 void ForeignSessionHandler::HandleDeleteForeignSession(
330 const base::ListValue
* args
) {
331 if (args
->GetSize() != 1U) {
332 LOG(ERROR
) << "Wrong number of args to deleteForeignSession";
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";
343 OpenTabsUIDelegate
* open_tabs
= GetOpenTabsUIDelegate(web_ui());
345 open_tabs
->DeleteForeignSession(session_tag
);
348 void ForeignSessionHandler::HandleSetForeignSessionCollapsed(
349 const base::ListValue
* args
) {
350 if (args
->GetSize() != 2U) {
351 LOG(ERROR
) << "Wrong number of args to setForeignSessionCollapsed";
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";
363 if (!args
->GetBoolean(1, &is_collapsed
)) {
364 LOG(ERROR
) << "Unable to extract boolean argument";
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
);
373 update
.Get()->SetBoolean(session_tag
, true);
375 update
.Get()->Remove(session_tag
, NULL
);
378 bool ForeignSessionHandler::SessionWindowToValue(
379 const ::sessions::SessionWindow
& window
,
380 base::DictionaryValue
* dictionary
) {
381 if (window
.tabs
.empty()) {
385 scoped_ptr
<base::ListValue
> tab_values(new base::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
<base::DictionaryValue
> tab_value(new base::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());
396 if (tab_values
->GetSize() == 0)
398 // The items which are to be written into |dictionary| are also described in
399 // chrome/browser/resources/ntp4/other_sessions.js in @typedef for WindowData.
400 // Please update it whenever you add or remove any keys here.
401 dictionary
->SetString("type", "window");
402 dictionary
->SetDouble("timestamp", modification_time
.ToInternalValue());
403 const base::TimeDelta last_synced
= base::Time::Now() - modification_time
;
404 // If clock skew leads to a future time, or we last synced less than a minute
405 // ago, output "Just now".
406 dictionary
->SetString("userVisibleTimestamp",
407 last_synced
< base::TimeDelta::FromMinutes(1) ?
408 l10n_util::GetStringUTF16(IDS_SYNC_TIME_JUST_NOW
) :
409 ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_ELAPSED
,
410 ui::TimeFormat::LENGTH_SHORT
, last_synced
));
411 dictionary
->SetInteger("sessionId", window
.window_id
.id());
412 dictionary
->Set("tabs", tab_values
.release());
416 } // namespace browser_sync