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/ntp/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 "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"
41 namespace browser_sync
{
43 // Maximum number of sessions we're going to display on the NTP
44 static const size_t kMaxSessionsToShow
= 10;
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
;
56 ForeignSessionHandler::ForeignSessionHandler() {
60 void ForeignSessionHandler::RegisterProfilePrefs(
61 user_prefs::PrefRegistrySyncable
* registry
) {
62 registry
->RegisterDictionaryPref(
63 prefs::kNtpCollapsedForeignSessions
,
64 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
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
);
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.";
85 if (tab
->navigations
.empty()) {
86 LOG(ERROR
) << "Foreign tab no longer has valid navigations.";
89 SessionRestore::RestoreForeignSessionTab(
90 web_ui
->GetWebContents(), *tab
, disposition
);
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
);
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.";
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()) :
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
);
123 bool ForeignSessionHandler::SessionTabToValue(
124 const SessionTab
& tab
,
125 base::DictionaryValue
* dictionary
) {
126 if (tab
.navigations
.empty())
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
))
137 NewTabUI::SetUrlTitleAndDirection(dictionary
, current_navigation
.title(),
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());
150 OpenTabsUIDelegate
* ForeignSessionHandler::GetOpenTabsUIDelegate(
151 content::WebUI
* web_ui
) {
152 Profile
* profile
= Profile::FromWebUI(web_ui
);
153 ProfileSyncService
* service
=
154 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
156 // Only return the delegate if it exists and it is done syncing sessions.
157 if (service
&& service
->ShouldPushChanges())
158 return service
->GetOpenTabsUIDelegate();
163 void ForeignSessionHandler::RegisterMessages() {
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)));
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
));
191 void ForeignSessionHandler::Observe(
193 const content::NotificationSource
& source
,
194 const content::NotificationDetails
& details
) {
195 base::ListValue list_value
;
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
);
203 case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE
:
204 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
:
205 HandleGetForeignSessions(&list_value
);
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
);
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
);
229 void ForeignSessionHandler::HandleGetForeignSessions(
230 const base::ListValue
* args
) {
231 OpenTabsUIDelegate
* open_tabs
= GetOpenTabsUIDelegate(web_ui());
232 std::vector
<const SyncedSession
*> sessions
;
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
);
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();
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
));
261 bool is_collapsed
= collapsed_sessions
->HasKey(session_tag
);
262 session_data
->SetBoolean("collapsed", is_collapsed
);
264 current_collapsed_sessions
->SetBoolean(session_tag
, true);
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());
276 session_data
->Set("windows", window_list
.release());
277 session_list
.Append(session_data
.release());
280 base::FundamentalValue
tab_sync_enabled(IsTabSyncEnabled());
281 web_ui()->CallJavascriptFunction("ntp.setForeignSessions",
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()
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.";
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.";
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.";
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
);
330 OpenForeignSessionWindows(web_ui(), session_string_value
, window_num
);
334 void ForeignSessionHandler::HandleDeleteForeignSession(
335 const base::ListValue
* args
) {
336 if (args
->GetSize() != 1U) {
337 LOG(ERROR
) << "Wrong number of args to deleteForeignSession";
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";
348 OpenTabsUIDelegate
* open_tabs
= GetOpenTabsUIDelegate(web_ui());
350 open_tabs
->DeleteForeignSession(session_tag
);
353 void ForeignSessionHandler::HandleSetForeignSessionCollapsed(
354 const base::ListValue
* args
) {
355 if (args
->GetSize() != 2U) {
356 LOG(ERROR
) << "Wrong number of args to setForeignSessionCollapsed";
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";
368 if (!args
->GetBoolean(1, &is_collapsed
)) {
369 LOG(ERROR
) << "Unable to extract boolean argument";
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
);
378 update
.Get()->SetBoolean(session_tag
, true);
380 update
.Get()->Remove(session_tag
, NULL
);
383 bool ForeignSessionHandler::SessionWindowToValue(
384 const SessionWindow
& window
,
385 base::DictionaryValue
* dictionary
) {
386 if (window
.tabs
.empty()) {
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());
401 if (tab_values
->GetSize() == 0)
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::TimeElapsed(last_synced
));
412 dictionary
->SetInteger("sessionId", window
.window_id
.id());
413 dictionary
->Set("tabs", tab_values
.release());
417 } // namespace browser_sync