1// Copyright (c) 2011 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/foreign_session_handler.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10#include "base/memory/scoped_vector.h"
11#include "base/string_number_conversions.h"
12#include "base/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/sessions/session_restore.h"
16#include "chrome/browser/sync/engine/syncapi.h"
17#include "chrome/browser/sync/profile_sync_service.h"
18#include "chrome/browser/ui/webui/new_tab_ui.h"
19#include "content/common/notification_details.h"
20#include "content/common/notification_service.h"
21#include "chrome/common/url_constants.h"
22
23namespace browser_sync {
24
25// Maximum number of session we're going to display on the NTP
26static const int kMaxSessionsToShow = 10;
27
28// Invalid value, used to note that we don't have a tab or window number.
29static const int kInvalidId = -1;
30
31ForeignSessionHandler::ForeignSessionHandler() {
32  Init();
33}
34
35void ForeignSessionHandler::RegisterMessages() {
36  web_ui_->RegisterMessageCallback("getForeignSessions",
37      NewCallback(this,
38      &ForeignSessionHandler::HandleGetForeignSessions));
39  web_ui_->RegisterMessageCallback("openForeignSession",
40      NewCallback(this,
41      &ForeignSessionHandler::HandleOpenForeignSession));
42}
43
44void ForeignSessionHandler::Init() {
45  registrar_.Add(this, NotificationType::SYNC_CONFIGURE_DONE,
46                 NotificationService::AllSources());
47  registrar_.Add(this, NotificationType::FOREIGN_SESSION_UPDATED,
48                 NotificationService::AllSources());
49  registrar_.Add(this, NotificationType::FOREIGN_SESSION_DISABLED,
50                 NotificationService::AllSources());
51}
52
53void ForeignSessionHandler::Observe(NotificationType type,
54                                    const NotificationSource& source,
55                                    const NotificationDetails& details) {
56  ListValue list_value;
57  switch (type.value) {
58    case NotificationType::SYNC_CONFIGURE_DONE:
59    case NotificationType::FOREIGN_SESSION_UPDATED:
60      HandleGetForeignSessions(&list_value);
61      break;
62    case NotificationType::FOREIGN_SESSION_DISABLED:
63      // Calling foreignSessions with empty list will automatically hide
64      // foreign session section.
65      web_ui_->CallJavascriptFunction("foreignSessions", list_value);
66      break;
67    default:
68      NOTREACHED();
69  }
70}
71
72SessionModelAssociator* ForeignSessionHandler::GetModelAssociator() {
73  ProfileSyncService* service = web_ui_->GetProfile()->GetProfileSyncService();
74  if (service == NULL)
75    return NULL;
76  // We only want to set the model associator if there is one, and it is done
77  // syncing sessions.
78  SessionModelAssociator* model_associator = service->
79      GetSessionModelAssociator();
80  if (model_associator == NULL ||
81      !service->ShouldPushChanges()) {
82    return NULL;
83  }
84  return web_ui_->GetProfile()->GetProfileSyncService()->
85      GetSessionModelAssociator();
86}
87
88void ForeignSessionHandler::HandleGetForeignSessions(const ListValue* args) {
89  SessionModelAssociator* associator = GetModelAssociator();
90  std::vector<const ForeignSession*> sessions;
91
92  if (associator == NULL) {
93    // Called before associator created, exit.
94    return;
95  }
96
97  // Note: we don't own the ForeignSessions themselves.
98  if (!associator->GetAllForeignSessions(&sessions)) {
99    LOG(ERROR) << "ForeignSessionHandler failed to get session data from"
100        "SessionModelAssociator.";
101    return;
102  }
103  int added_count = 0;
104  ListValue session_list;
105  for (std::vector<const ForeignSession*>::const_iterator i =
106      sessions.begin(); i != sessions.end() &&
107      added_count < kMaxSessionsToShow; ++i) {
108    const ForeignSession* foreign_session = *i;
109    scoped_ptr<ListValue> window_list(new ListValue());
110    for (std::vector<SessionWindow*>::const_iterator it =
111        foreign_session->windows.begin(); it != foreign_session->windows.end();
112        ++it) {
113      SessionWindow* window = *it;
114      scoped_ptr<DictionaryValue> window_data(new DictionaryValue());
115      if (SessionWindowToValue(*window, window_data.get())) {
116        window_data->SetString("sessionTag",
117            foreign_session->foreign_session_tag);
118
119        // Give ownership to |list_value|.
120        window_list->Append(window_data.release());
121      }
122    }
123    added_count++;
124
125    // Give ownership to |session_list|.
126    session_list.Append(window_list.release());
127  }
128  web_ui_->CallJavascriptFunction("foreignSessions", session_list);
129}
130
131void ForeignSessionHandler::HandleOpenForeignSession(
132    const ListValue* args) {
133  size_t num_args = args->GetSize();
134  if (num_args > 3U || num_args == 0) {
135    LOG(ERROR) << "openForeignWindow called with only " << args->GetSize()
136               << " arguments.";
137    return;
138  }
139
140  // Extract the machine tag (always provided).
141  std::string session_string_value;
142  if (!args->GetString(0, &session_string_value)) {
143    LOG(ERROR) << "Failed to extract session tag.";
144    return;
145  }
146
147  // Extract window number.
148  std::string window_num_str;
149  int window_num = kInvalidId;
150  if (num_args >= 2 && (!args->GetString(1, &window_num_str) ||
151      !base::StringToInt(window_num_str, &window_num))) {
152    LOG(ERROR) << "Failed to extract window number.";
153    return;
154  }
155
156  // Extract tab id.
157  std::string tab_id_str;
158  SessionID::id_type tab_id = kInvalidId;
159  if (num_args == 3 && (!args->GetString(2, &tab_id_str) ||
160      !base::StringToInt(tab_id_str, &tab_id))) {
161    LOG(ERROR) << "Failed to extract tab SessionID.";
162    return;
163  }
164
165  SessionModelAssociator* associator = GetModelAssociator();
166
167  if (tab_id != kInvalidId) {
168    // We don't actually care about |window_num|, this is just a sanity check.
169    DCHECK_LT(kInvalidId, window_num);
170    const SessionTab* tab;
171    if (!associator->GetForeignTab(session_string_value, tab_id, &tab)) {
172      LOG(ERROR) << "Failed to load foreign tab.";
173      return;
174    }
175    SessionRestore::RestoreForeignSessionTab(web_ui_->GetProfile(), *tab);
176  } else {
177    std::vector<SessionWindow*> windows;
178    // Note: we don't own the ForeignSessions themselves.
179    if (!associator->GetForeignSession(session_string_value, &windows)) {
180      LOG(ERROR) << "ForeignSessionHandler failed to get session data from"
181          "SessionModelAssociator.";
182      return;
183    }
184    std::vector<SessionWindow*>::const_iterator iter_begin = windows.begin() +
185        ((window_num == kInvalidId) ? 0 : window_num);
186    std::vector<SessionWindow*>::const_iterator iter_end =
187        ((window_num == kInvalidId) ?
188        std::vector<SessionWindow*>::const_iterator(windows.end()) :
189        iter_begin+1);
190    SessionRestore::RestoreForeignSessionWindows(web_ui_->GetProfile(),
191                                                 iter_begin,
192                                                 iter_end);
193  }
194}
195
196bool ForeignSessionHandler::SessionTabToValue(
197    const SessionTab& tab,
198    DictionaryValue* dictionary) {
199  if (tab.navigations.empty())
200    return false;
201  int selected_index = tab.current_navigation_index;
202  selected_index = std::max(
203      0,
204      std::min(selected_index,
205               static_cast<int>(tab.navigations.size() - 1)));
206  const TabNavigation& current_navigation =
207      tab.navigations.at(selected_index);
208  if (current_navigation.virtual_url() == GURL(chrome::kChromeUINewTabURL))
209    return false;
210  NewTabUI::SetURLTitleAndDirection(dictionary, current_navigation.title(),
211                                    current_navigation.virtual_url());
212  dictionary->SetString("type", "tab");
213  dictionary->SetDouble("timestamp",
214                        static_cast<double>(tab.timestamp.ToInternalValue()));
215  dictionary->SetInteger("sessionId", tab.tab_id.id());
216  return true;
217}
218
219bool ForeignSessionHandler::SessionWindowToValue(
220    const SessionWindow& window,
221    DictionaryValue* dictionary) {
222  if (window.tabs.empty()) {
223    NOTREACHED();
224    return false;
225  }
226  scoped_ptr<ListValue> tab_values(new ListValue());
227  for (size_t i = 0; i < window.tabs.size(); ++i) {
228    scoped_ptr<DictionaryValue> tab_value(new DictionaryValue());
229    if (SessionTabToValue(*window.tabs[i], tab_value.get()))
230      tab_values->Append(tab_value.release());
231  }
232  if (tab_values->GetSize() == 0)
233    return false;
234  dictionary->SetString("type", "window");
235  dictionary->SetDouble("timestamp",
236      static_cast<double>(window.timestamp.ToInternalValue()));
237  dictionary->SetInteger("sessionId", window.window_id.id());
238  dictionary->Set("tabs", tab_values.release());
239  return true;
240}
241
242}  // namespace browser_sync
243