- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / ntp / foreign_session_handler.cc
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 "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"
40
41 namespace browser_sync {
42
43 // Maximum number of sessions we're going to display on the NTP
44 static const size_t kMaxSessionsToShow = 10;
45
46 namespace {
47
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;
52 }
53
54 }  // namepace
55
56 ForeignSessionHandler::ForeignSessionHandler() {
57 }
58
59 // static
60 void ForeignSessionHandler::RegisterProfilePrefs(
61     user_prefs::PrefRegistrySyncable* registry) {
62   registry->RegisterDictionaryPref(
63       prefs::kNtpCollapsedForeignSessions,
64       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
65 }
66
67 // static
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   SessionModelAssociator* associator = GetModelAssociator(web_ui);
75   if (!associator)
76     return;
77
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 (!associator->GetForeignTab(session_string_value, tab_id, &tab)) {
82     LOG(ERROR) << "Failed to load foreign tab.";
83     return;
84   }
85   if (tab->navigations.empty()) {
86     LOG(ERROR) << "Foreign tab no longer has valid navigations.";
87     return;
88   }
89   SessionRestore::RestoreForeignSessionTab(
90       web_ui->GetWebContents(), *tab, disposition);
91 }
92
93 // static
94 void ForeignSessionHandler::OpenForeignSessionWindows(
95     content::WebUI* web_ui,
96     const std::string& session_string_value,
97     SessionID::id_type window_num) {
98   SessionModelAssociator* associator = GetModelAssociator(web_ui);
99   if (!associator)
100     return;
101
102   std::vector<const SessionWindow*> windows;
103   // Note: we don't own the ForeignSessions themselves.
104   if (!associator->GetForeignSession(session_string_value, &windows)) {
105     LOG(ERROR) << "ForeignSessionHandler failed to get session data from"
106         "SessionModelAssociator.";
107     return;
108   }
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()) :
114       iter_begin + 1;
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);
120 }
121
122 // static
123 bool ForeignSessionHandler::SessionTabToValue(
124     const SessionTab& tab,
125     DictionaryValue* dictionary) {
126   if (tab.navigations.empty())
127     return false;
128
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))
135     return false;
136
137   NewTabUI::SetUrlTitleAndDirection(dictionary, current_navigation.title(),
138                                     tab_url);
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());
146   return true;
147 }
148
149 // static
150 SessionModelAssociator* ForeignSessionHandler::GetModelAssociator(
151     content::WebUI* web_ui) {
152   Profile* profile = Profile::FromWebUI(web_ui);
153   ProfileSyncService* service =
154       ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
155
156   // Only return the associator if it exists and it is done syncing sessions.
157   if (service && service->ShouldPushChanges())
158     return service->GetSessionModelAssociator();
159
160   return NULL;
161 }
162
163 void ForeignSessionHandler::RegisterMessages() {
164   Init();
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)));
177 }
178
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));
189 }
190
191 void ForeignSessionHandler::Observe(
192     int type,
193     const content::NotificationSource& source,
194     const content::NotificationDetails& details) {
195   ListValue list_value;
196
197   switch (type) {
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);
202       // Fall through.
203     case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE:
204     case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
205       HandleGetForeignSessions(&list_value);
206       break;
207     default:
208       NOTREACHED();
209   }
210 }
211
212
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);
218 }
219
220 string16 ForeignSessionHandler::FormatSessionTime(const base::Time& time) {
221   // Return a time like "1 hour ago", "2 days ago", etc.
222   base::Time now = base::Time::Now();
223   // TimeElapsed does not support negative TimeDelta values, so then we use 0.
224   return ui::TimeFormat::TimeElapsed(
225       now < time ? base::TimeDelta() : now - time);
226 }
227
228 void ForeignSessionHandler::HandleGetForeignSessions(const ListValue* args) {
229   SessionModelAssociator* associator = GetModelAssociator(web_ui());
230   std::vector<const SyncedSession*> sessions;
231
232   ListValue session_list;
233   if (associator && associator->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     DictionaryValue* current_collapsed_sessions = pref_update.Get();
243     scoped_ptr<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<DictionaryValue> session_data(new DictionaryValue());
252       session_data->SetString("tag", session_tag);
253       session_data->SetString("name", session->session_name);
254       session_data->SetString("deviceType", session->DeviceTypeAsString());
255       session_data->SetString("modifiedTime",
256                               FormatSessionTime(session->modified_time));
257
258       bool is_collapsed = collapsed_sessions->HasKey(session_tag);
259       session_data->SetBoolean("collapsed", is_collapsed);
260       if (is_collapsed)
261         current_collapsed_sessions->SetBoolean(session_tag, true);
262
263       scoped_ptr<ListValue> window_list(new ListValue());
264       for (SyncedSession::SyncedWindowMap::const_iterator it =
265            session->windows.begin(); it != session->windows.end(); ++it) {
266         SessionWindow* window = it->second;
267         scoped_ptr<DictionaryValue> window_data(new DictionaryValue());
268         if (SessionWindowToValue(*window, window_data.get()))
269           window_list->Append(window_data.release());
270       }
271
272       session_data->Set("windows", window_list.release());
273       session_list.Append(session_data.release());
274     }
275   }
276   base::FundamentalValue tab_sync_enabled(IsTabSyncEnabled());
277   web_ui()->CallJavascriptFunction("ntp.setForeignSessions",
278                                    session_list,
279                                    tab_sync_enabled);
280 }
281
282 void ForeignSessionHandler::HandleOpenForeignSession(const 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()
291                << " arguments.";
292     return;
293   }
294
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.";
299     return;
300   }
301
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.";
308     return;
309   }
310
311   // Extract tab id.
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.";
317     return;
318   }
319
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);
324   } else {
325     OpenForeignSessionWindows(web_ui(), session_string_value, window_num);
326   }
327 }
328
329 void ForeignSessionHandler::HandleDeleteForeignSession(const ListValue* args) {
330   if (args->GetSize() != 1U) {
331     LOG(ERROR) << "Wrong number of args to deleteForeignSession";
332     return;
333   }
334
335   // Get the session tag argument (required).
336   std::string session_tag;
337   if (!args->GetString(0, &session_tag)) {
338     LOG(ERROR) << "Unable to extract session tag";
339     return;
340   }
341
342   SessionModelAssociator* associator = GetModelAssociator(web_ui());
343   if (associator)
344     associator->DeleteForeignSession(session_tag);
345 }
346
347 void ForeignSessionHandler::HandleSetForeignSessionCollapsed(
348     const ListValue* args) {
349   if (args->GetSize() != 2U) {
350     LOG(ERROR) << "Wrong number of args to setForeignSessionCollapsed";
351     return;
352   }
353
354   // Get the session tag argument (required).
355   std::string session_tag;
356   if (!args->GetString(0, &session_tag)) {
357     LOG(ERROR) << "Unable to extract session tag";
358     return;
359   }
360
361   bool is_collapsed;
362   if (!args->GetBoolean(1, &is_collapsed)) {
363     LOG(ERROR) << "Unable to extract boolean argument";
364     return;
365   }
366
367   // Store session tags for collapsed sessions in a preference so that the
368   // collapsed state persists.
369   PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
370   DictionaryPrefUpdate update(prefs, prefs::kNtpCollapsedForeignSessions);
371   if (is_collapsed)
372     update.Get()->SetBoolean(session_tag, true);
373   else
374     update.Get()->Remove(session_tag, NULL);
375 }
376
377 bool ForeignSessionHandler::SessionWindowToValue(
378     const SessionWindow& window,
379     DictionaryValue* dictionary) {
380   if (window.tabs.empty()) {
381     NOTREACHED();
382     return false;
383   }
384   scoped_ptr<ListValue> tab_values(new ListValue());
385   // Calculate the last |modification_time| for all entries within a window.
386   base::Time modification_time = window.timestamp;
387   for (size_t i = 0; i < window.tabs.size(); ++i) {
388     scoped_ptr<DictionaryValue> tab_value(new DictionaryValue());
389     if (SessionTabToValue(*window.tabs[i], tab_value.get())) {
390       modification_time = std::max(modification_time,
391                                    window.tabs[i]->timestamp);
392       tab_values->Append(tab_value.release());
393     }
394   }
395   if (tab_values->GetSize() == 0)
396     return false;
397   dictionary->SetString("type", "window");
398   dictionary->SetDouble("timestamp", modification_time.ToInternalValue());
399   const base::TimeDelta last_synced = base::Time::Now() - modification_time;
400   // If clock skew leads to a future time, or we last synced less than a minute
401   // ago, output "Just now".
402   dictionary->SetString("userVisibleTimestamp",
403       last_synced < base::TimeDelta::FromMinutes(1) ?
404           l10n_util::GetStringUTF16(IDS_SYNC_TIME_JUST_NOW) :
405           ui::TimeFormat::TimeElapsed(last_synced));
406   dictionary->SetInteger("sessionId", window.window_id.id());
407   dictionary->Set("tabs", tab_values.release());
408   return true;
409 }
410
411 }  // namespace browser_sync