base_session_service.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/sessions/base_session_service.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/pickle.h"
10#include "base/stl_util.h"
11#include "base/threading/thread.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/sessions/session_backend.h"
15#include "chrome/browser/sessions/session_types.h"
16#include "chrome/common/chrome_switches.h"
17#include "content/public/browser/browser_thread.h"
18#include "content/public/browser/navigation_entry.h"
19#include "content/public/common/referrer.h"
20#include "webkit/glue/webkit_glue.h"
21
22using content::BrowserThread;
23using content::NavigationEntry;
24
25// BaseSessionService ---------------------------------------------------------
26
27namespace {
28
29// Helper used by CreateUpdateTabNavigationCommand(). It writes |str| to
30// |pickle|, if and only if |str| fits within (|max_bytes| - |*bytes_written|).
31// |bytes_written| is incremented to reflect the data written.
32void WriteStringToPickle(Pickle& pickle, int* bytes_written, int max_bytes,
33                         const std::string& str) {
34  int num_bytes = str.size() * sizeof(char);
35  if (*bytes_written + num_bytes < max_bytes) {
36    *bytes_written += num_bytes;
37    pickle.WriteString(str);
38  } else {
39    pickle.WriteString(std::string());
40  }
41}
42
43// Helper used by ScheduleGetLastSessionCommands. It runs callback on TaskRunner
44// thread if it's not canceled.
45void RunIfNotCanceled(
46    const CancelableTaskTracker::IsCanceledCallback& is_canceled,
47    const BaseSessionService::InternalGetCommandsCallback& callback,
48    ScopedVector<SessionCommand> commands) {
49  if (is_canceled.Run())
50    return;
51  callback.Run(commands.Pass());
52}
53
54void PostOrRunInternalGetCommandsCallback(
55    base::TaskRunner* task_runner,
56    const BaseSessionService::InternalGetCommandsCallback& callback,
57    ScopedVector<SessionCommand> commands) {
58  if (task_runner->RunsTasksOnCurrentThread()) {
59    callback.Run(commands.Pass());
60  } else {
61    task_runner->PostTask(FROM_HERE,
62                          base::Bind(callback, base::Passed(&commands)));
63  }
64}
65
66}  // namespace
67
68// Delay between when a command is received, and when we save it to the
69// backend.
70static const int kSaveDelayMS = 2500;
71
72// static
73const int BaseSessionService::max_persist_navigation_count = 6;
74
75BaseSessionService::BaseSessionService(SessionType type,
76                                       Profile* profile,
77                                       const base::FilePath& path)
78    : profile_(profile),
79      weak_factory_(this),
80      pending_reset_(false),
81      commands_since_reset_(0) {
82  if (profile) {
83    // We should never be created when incognito.
84    DCHECK(!profile->IsOffTheRecord());
85  }
86  backend_ = new SessionBackend(type, profile_ ? profile_->GetPath() : path);
87  DCHECK(backend_.get());
88
89  // SessionBackend::Init() cannot be scheduled to be called here. There are
90  // service processes which create the BaseSessionService, but they should not
91  // initialize the backend. If they do, the backend will cycle the session
92  // restore files. That in turn prevents the session restore from working when
93  // the normal chromium process is launched. Normally, the backend will be
94  // initialized before it's actually used. However, if we're running as a part
95  // of a test, it must be initialized now.
96  if (!RunningInProduction())
97    backend_->Init();
98}
99
100BaseSessionService::~BaseSessionService() {
101}
102
103void BaseSessionService::DeleteLastSession() {
104  RunTaskOnBackendThread(
105      FROM_HERE,
106      base::Bind(&SessionBackend::DeleteLastSession, backend()));
107}
108
109void BaseSessionService::ScheduleCommand(SessionCommand* command) {
110  DCHECK(command);
111  commands_since_reset_++;
112  pending_commands_.push_back(command);
113  StartSaveTimer();
114}
115
116void BaseSessionService::StartSaveTimer() {
117  // Don't start a timer when testing (profile == NULL or
118  // MessageLoop::current() is NULL).
119  if (MessageLoop::current() && profile() && !weak_factory_.HasWeakPtrs()) {
120    MessageLoop::current()->PostDelayedTask(
121        FROM_HERE,
122        base::Bind(&BaseSessionService::Save, weak_factory_.GetWeakPtr()),
123        base::TimeDelta::FromMilliseconds(kSaveDelayMS));
124  }
125}
126
127void BaseSessionService::Save() {
128  DCHECK(backend());
129
130  if (pending_commands_.empty())
131    return;
132
133  RunTaskOnBackendThread(
134      FROM_HERE,
135      base::Bind(&SessionBackend::AppendCommands, backend(),
136                 new std::vector<SessionCommand*>(pending_commands_),
137                 pending_reset_));
138
139  // Backend took ownership of commands.
140  pending_commands_.clear();
141
142  if (pending_reset_) {
143    commands_since_reset_ = 0;
144    pending_reset_ = false;
145  }
146}
147
148SessionCommand* BaseSessionService::CreateUpdateTabNavigationCommand(
149    SessionID::id_type command_id,
150    SessionID::id_type tab_id,
151    const sessions::SerializedNavigationEntry& navigation) {
152  // Use pickle to handle marshalling.
153  Pickle pickle;
154  pickle.WriteInt(tab_id);
155  // We only allow navigations up to 63k (which should be completely
156  // reasonable).
157  static const size_t max_state_size =
158      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
159  navigation.WriteToPickle(max_state_size, &pickle);
160  return new SessionCommand(command_id, pickle);
161}
162
163SessionCommand* BaseSessionService::CreateSetTabExtensionAppIDCommand(
164    SessionID::id_type command_id,
165    SessionID::id_type tab_id,
166    const std::string& extension_id) {
167  // Use pickle to handle marshalling.
168  Pickle pickle;
169  pickle.WriteInt(tab_id);
170
171  // Enforce a max for ids. They should never be anywhere near this size.
172  static const SessionCommand::size_type max_id_size =
173      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
174
175  int bytes_written = 0;
176
177  WriteStringToPickle(pickle, &bytes_written, max_id_size, extension_id);
178
179  return new SessionCommand(command_id, pickle);
180}
181
182SessionCommand* BaseSessionService::CreateSetTabUserAgentOverrideCommand(
183    SessionID::id_type command_id,
184    SessionID::id_type tab_id,
185    const std::string& user_agent_override) {
186  // Use pickle to handle marshalling.
187  Pickle pickle;
188  pickle.WriteInt(tab_id);
189
190  // Enforce a max for the user agent length.  They should never be anywhere
191  // near this size.
192  static const SessionCommand::size_type max_user_agent_size =
193      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
194
195  int bytes_written = 0;
196
197  WriteStringToPickle(pickle, &bytes_written, max_user_agent_size,
198      user_agent_override);
199
200  return new SessionCommand(command_id, pickle);
201}
202
203SessionCommand* BaseSessionService::CreateSetWindowAppNameCommand(
204    SessionID::id_type command_id,
205    SessionID::id_type window_id,
206    const std::string& app_name) {
207  // Use pickle to handle marshalling.
208  Pickle pickle;
209  pickle.WriteInt(window_id);
210
211  // Enforce a max for ids. They should never be anywhere near this size.
212  static const SessionCommand::size_type max_id_size =
213      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
214
215  int bytes_written = 0;
216
217  WriteStringToPickle(pickle, &bytes_written, max_id_size, app_name);
218
219  return new SessionCommand(command_id, pickle);
220}
221
222bool BaseSessionService::RestoreUpdateTabNavigationCommand(
223    const SessionCommand& command,
224    sessions::SerializedNavigationEntry* navigation,
225    SessionID::id_type* tab_id) {
226  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
227  if (!pickle.get())
228    return false;
229  PickleIterator iterator(*pickle);
230  return
231      pickle->ReadInt(&iterator, tab_id) &&
232      navigation->ReadFromPickle(&iterator);
233}
234
235bool BaseSessionService::RestoreSetTabExtensionAppIDCommand(
236    const SessionCommand& command,
237    SessionID::id_type* tab_id,
238    std::string* extension_app_id) {
239  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
240  if (!pickle.get())
241    return false;
242
243  PickleIterator iterator(*pickle);
244  return pickle->ReadInt(&iterator, tab_id) &&
245      pickle->ReadString(&iterator, extension_app_id);
246}
247
248bool BaseSessionService::RestoreSetTabUserAgentOverrideCommand(
249    const SessionCommand& command,
250    SessionID::id_type* tab_id,
251    std::string* user_agent_override) {
252  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
253  if (!pickle.get())
254    return false;
255
256  PickleIterator iterator(*pickle);
257  return pickle->ReadInt(&iterator, tab_id) &&
258      pickle->ReadString(&iterator, user_agent_override);
259}
260
261bool BaseSessionService::RestoreSetWindowAppNameCommand(
262    const SessionCommand& command,
263    SessionID::id_type* window_id,
264    std::string* app_name) {
265  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
266  if (!pickle.get())
267    return false;
268
269  PickleIterator iterator(*pickle);
270  return pickle->ReadInt(&iterator, window_id) &&
271      pickle->ReadString(&iterator, app_name);
272}
273
274bool BaseSessionService::ShouldTrackEntry(const GURL& url) {
275  return url.is_valid();
276}
277
278CancelableTaskTracker::TaskId
279    BaseSessionService::ScheduleGetLastSessionCommands(
280    const InternalGetCommandsCallback& callback,
281    CancelableTaskTracker* tracker) {
282  CancelableTaskTracker::IsCanceledCallback is_canceled;
283  CancelableTaskTracker::TaskId id = tracker->NewTrackedTaskId(&is_canceled);
284
285  InternalGetCommandsCallback run_if_not_canceled =
286      base::Bind(&RunIfNotCanceled, is_canceled, callback);
287
288  InternalGetCommandsCallback callback_runner =
289      base::Bind(&PostOrRunInternalGetCommandsCallback,
290                 base::MessageLoopProxy::current(), run_if_not_canceled);
291
292  RunTaskOnBackendThread(
293      FROM_HERE,
294      base::Bind(&SessionBackend::ReadLastSessionCommands, backend(),
295                 is_canceled, callback_runner));
296  return id;
297}
298
299bool BaseSessionService::RunTaskOnBackendThread(
300    const tracked_objects::Location& from_here,
301    const base::Closure& task) {
302  if (profile_ && BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) {
303    return BrowserThread::PostTask(BrowserThread::FILE, from_here, task);
304  } else {
305    // Fall back to executing on the main thread if the file thread
306    // has gone away (around shutdown time) or if we're running as
307    // part of a unit test that does not set profile_.
308    task.Run();
309    return true;
310  }
311}
312
313bool BaseSessionService::RunningInProduction() const {
314  return profile_ && BrowserThread::IsMessageLoopValid(BrowserThread::FILE);
315}
316