1// Copyright (c) 2006-2008 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/sessions/base_session_service.h"
6
7#include "base/pickle.h"
8#include "base/stl_util-inl.h"
9#include "base/threading/thread.h"
10#include "chrome/browser/browser_process.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/sessions/session_backend.h"
13#include "chrome/browser/sessions/session_types.h"
14#include "content/browser/tab_contents/navigation_entry.h"
15#include "webkit/glue/webkit_glue.h"
16
17// InternalGetCommandsRequest -------------------------------------------------
18
19BaseSessionService::InternalGetCommandsRequest::~InternalGetCommandsRequest() {
20  STLDeleteElements(&commands);
21}
22
23// BaseSessionService ---------------------------------------------------------
24
25namespace {
26
27// Helper used by CreateUpdateTabNavigationCommand(). It writes |str| to
28// |pickle|, if and only if |str| fits within (|max_bytes| - |*bytes_written|).
29// |bytes_written| is incremented to reflect the data written.
30void WriteStringToPickle(Pickle& pickle, int* bytes_written, int max_bytes,
31                         const std::string& str) {
32  int num_bytes = str.size() * sizeof(char);
33  if (*bytes_written + num_bytes < max_bytes) {
34    *bytes_written += num_bytes;
35    pickle.WriteString(str);
36  } else {
37    pickle.WriteString(std::string());
38  }
39}
40
41// string16 version of WriteStringToPickle.
42void WriteString16ToPickle(Pickle& pickle, int* bytes_written, int max_bytes,
43                           const string16& str) {
44  int num_bytes = str.size() * sizeof(char16);
45  if (*bytes_written + num_bytes < max_bytes) {
46    *bytes_written += num_bytes;
47    pickle.WriteString16(str);
48  } else {
49    pickle.WriteString16(string16());
50  }
51}
52
53}  // namespace
54
55// Delay between when a command is received, and when we save it to the
56// backend.
57static const int kSaveDelayMS = 2500;
58
59// static
60const int BaseSessionService::max_persist_navigation_count = 6;
61
62BaseSessionService::BaseSessionService(SessionType type,
63                                       Profile* profile,
64                                       const FilePath& path)
65    : profile_(profile),
66      path_(path),
67      backend_thread_(NULL),
68      ALLOW_THIS_IN_INITIALIZER_LIST(save_factory_(this)),
69      pending_reset_(false),
70      commands_since_reset_(0) {
71  if (profile) {
72    // We should never be created when incognito.
73    DCHECK(!profile->IsOffTheRecord());
74  }
75  backend_ = new SessionBackend(type,
76      profile_ ? profile_->GetPath() : path_);
77  DCHECK(backend_.get());
78  backend_thread_ = g_browser_process->file_thread();
79  if (!backend_thread_)
80    backend_->Init();
81  // If backend_thread is non-null, backend will init itself as appropriate.
82}
83
84BaseSessionService::~BaseSessionService() {
85}
86
87void BaseSessionService::DeleteLastSession() {
88  if (!backend_thread()) {
89    backend()->DeleteLastSession();
90  } else {
91    backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
92        backend(), &SessionBackend::DeleteLastSession));
93  }
94}
95
96void BaseSessionService::ScheduleCommand(SessionCommand* command) {
97  DCHECK(command);
98  commands_since_reset_++;
99  pending_commands_.push_back(command);
100  StartSaveTimer();
101}
102
103void BaseSessionService::StartSaveTimer() {
104  // Don't start a timer when testing (profile == NULL or
105  // MessageLoop::current() is NULL).
106  if (MessageLoop::current() && profile() && save_factory_.empty()) {
107    MessageLoop::current()->PostDelayedTask(FROM_HERE,
108        save_factory_.NewRunnableMethod(&BaseSessionService::Save),
109        kSaveDelayMS);
110  }
111}
112
113void BaseSessionService::Save() {
114  DCHECK(backend());
115
116  if (pending_commands_.empty())
117    return;
118
119  if (!backend_thread()) {
120    backend()->AppendCommands(
121        new std::vector<SessionCommand*>(pending_commands_), pending_reset_);
122  } else {
123    backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
124        backend(), &SessionBackend::AppendCommands,
125        new std::vector<SessionCommand*>(pending_commands_),
126        pending_reset_));
127  }
128  // Backend took ownership of commands.
129  pending_commands_.clear();
130
131  if (pending_reset_) {
132    commands_since_reset_ = 0;
133    pending_reset_ = false;
134  }
135}
136
137SessionCommand* BaseSessionService::CreateUpdateTabNavigationCommand(
138    SessionID::id_type command_id,
139    SessionID::id_type tab_id,
140    int index,
141    const NavigationEntry& entry) {
142  // Use pickle to handle marshalling.
143  Pickle pickle;
144  pickle.WriteInt(tab_id);
145  pickle.WriteInt(index);
146
147  // We only allow navigations up to 63k (which should be completely
148  // reasonable). On the off chance we get one that is too big, try to
149  // keep the url.
150
151  // Bound the string data (which is variable length) to
152  // |max_state_size bytes| bytes.
153  static const SessionCommand::size_type max_state_size =
154      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
155
156  int bytes_written = 0;
157
158  WriteStringToPickle(pickle, &bytes_written, max_state_size,
159                      entry.virtual_url().spec());
160
161  WriteString16ToPickle(pickle, &bytes_written, max_state_size, entry.title());
162
163  if (entry.has_post_data()) {
164    // Remove the form data, it may contain sensitive information.
165    WriteStringToPickle(pickle, &bytes_written, max_state_size,
166        webkit_glue::RemoveFormDataFromHistoryState(entry.content_state()));
167  } else {
168    WriteStringToPickle(pickle, &bytes_written, max_state_size,
169                        entry.content_state());
170  }
171
172  pickle.WriteInt(entry.transition_type());
173  int type_mask = entry.has_post_data() ? TabNavigation::HAS_POST_DATA : 0;
174  pickle.WriteInt(type_mask);
175
176  WriteStringToPickle(pickle, &bytes_written, max_state_size,
177      entry.referrer().is_valid() ? entry.referrer().spec() : std::string());
178
179  // Adding more data? Be sure and update TabRestoreService too.
180  return new SessionCommand(command_id, pickle);
181}
182
183SessionCommand* BaseSessionService::CreateSetTabExtensionAppIDCommand(
184    SessionID::id_type command_id,
185    SessionID::id_type tab_id,
186    const std::string& extension_id) {
187  // Use pickle to handle marshalling.
188  Pickle pickle;
189  pickle.WriteInt(tab_id);
190
191  // Enforce a max for ids. They should never be anywhere near this size.
192  static const SessionCommand::size_type max_id_size =
193      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
194
195  int bytes_written = 0;
196
197  WriteStringToPickle(pickle, &bytes_written, max_id_size, extension_id);
198
199  return new SessionCommand(command_id, pickle);
200}
201
202bool BaseSessionService::RestoreUpdateTabNavigationCommand(
203    const SessionCommand& command,
204    TabNavigation* navigation,
205    SessionID::id_type* tab_id) {
206  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
207  if (!pickle.get())
208    return false;
209  void* iterator = NULL;
210  std::string url_spec;
211  if (!pickle->ReadInt(&iterator, tab_id) ||
212      !pickle->ReadInt(&iterator, &(navigation->index_)) ||
213      !pickle->ReadString(&iterator, &url_spec) ||
214      !pickle->ReadString16(&iterator, &(navigation->title_)) ||
215      !pickle->ReadString(&iterator, &(navigation->state_)) ||
216      !pickle->ReadInt(&iterator,
217                       reinterpret_cast<int*>(&(navigation->transition_))))
218    return false;
219  // type_mask did not always exist in the written stream. As such, we
220  // don't fail if it can't be read.
221  bool has_type_mask = pickle->ReadInt(&iterator, &(navigation->type_mask_));
222
223  if (has_type_mask) {
224    // the "referrer" property was added after type_mask to the written
225    // stream. As such, we don't fail if it can't be read.
226    std::string referrer_spec;
227    pickle->ReadString(&iterator, &referrer_spec);
228    if (!referrer_spec.empty())
229      navigation->referrer_ = GURL(referrer_spec);
230  }
231
232  navigation->virtual_url_ = GURL(url_spec);
233  return true;
234}
235
236bool BaseSessionService::RestoreSetTabExtensionAppIDCommand(
237    const SessionCommand& command,
238    SessionID::id_type* tab_id,
239    std::string* extension_app_id) {
240  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
241  if (!pickle.get())
242    return false;
243
244  void* iterator = NULL;
245  return pickle->ReadInt(&iterator, tab_id) &&
246      pickle->ReadString(&iterator, extension_app_id);
247}
248
249bool BaseSessionService::ShouldTrackEntry(const NavigationEntry& entry) {
250  return entry.virtual_url().is_valid();
251}
252
253bool BaseSessionService::ShouldTrackEntry(const TabNavigation& navigation) {
254  return navigation.virtual_url().is_valid();
255}
256
257BaseSessionService::Handle BaseSessionService::ScheduleGetLastSessionCommands(
258    InternalGetCommandsRequest* request,
259    CancelableRequestConsumerBase* consumer) {
260  scoped_refptr<InternalGetCommandsRequest> request_wrapper(request);
261  AddRequest(request, consumer);
262  if (backend_thread()) {
263    backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
264        backend(), &SessionBackend::ReadLastSessionCommands, request_wrapper));
265  } else {
266    backend()->ReadLastSessionCommands(request);
267  }
268  return request->handle();
269}
270
271BaseSessionService::Handle
272    BaseSessionService::ScheduleGetCurrentSessionCommands(
273        InternalGetCommandsRequest* request,
274        CancelableRequestConsumerBase* consumer) {
275  scoped_refptr<InternalGetCommandsRequest> request_wrapper(request);
276  AddRequest(request, consumer);
277  if (backend_thread()) {
278    backend_thread()->message_loop()->PostTask(FROM_HERE,
279        NewRunnableMethod(backend(),
280                          &SessionBackend::ReadCurrentSessionCommands,
281                          request_wrapper));
282  } else {
283    backend()->ReadCurrentSessionCommands(request);
284  }
285  return request->handle();
286}
287