• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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).
SortSessionsByRecency(const SyncedSession * s1,const SyncedSession * s2)49 bool SortSessionsByRecency(const SyncedSession* s1, const SyncedSession* s2) {
50   return s1->modified_time > s2->modified_time;
51 }
52 
53 }  // namepace
54 
ForeignSessionHandler()55 ForeignSessionHandler::ForeignSessionHandler() {
56 }
57 
58 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)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
OpenForeignSessionTab(content::WebUI * web_ui,const std::string & session_string_value,SessionID::id_type window_num,SessionID::id_type tab_id,const WindowOpenDisposition & disposition)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
OpenForeignSessionWindows(content::WebUI * web_ui,const std::string & session_string_value,SessionID::id_type window_num)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
SessionTabToValue(const SessionTab & tab,base::DictionaryValue * dictionary)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
GetOpenTabsUIDelegate(content::WebUI * web_ui)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 
RegisterMessages()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 
Init()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 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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 
IsTabSyncEnabled()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 
FormatSessionTime(const base::Time & time)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 
HandleGetForeignSessions(const base::ListValue * args)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 
HandleOpenForeignSession(const base::ListValue * args)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 
HandleDeleteForeignSession(const base::ListValue * args)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 
HandleSetForeignSessionCollapsed(const base::ListValue * args)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 
SessionWindowToValue(const SessionWindow & window,base::DictionaryValue * dictionary)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