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