base_session_service.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 "chrome/common/url_constants.h"
18#include "content/public/browser/browser_thread.h"
19#include "content/public/browser/navigation_entry.h"
20#include "content/public/common/referrer.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 base::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      pending_reset_(false),
80      commands_since_reset_(0),
81      sequence_token_(
82          content::BrowserThread::GetBlockingPool()->GetSequenceToken()),
83      weak_factory_(this) {
84  if (profile) {
85    // We should never be created when incognito.
86    DCHECK(!profile->IsOffTheRecord());
87  }
88  backend_ = new SessionBackend(type, profile_ ? profile_->GetPath() : path);
89  DCHECK(backend_.get());
90}
91
92BaseSessionService::~BaseSessionService() {
93}
94
95void BaseSessionService::DeleteLastSession() {
96  RunTaskOnBackendThread(
97      FROM_HERE,
98      base::Bind(&SessionBackend::DeleteLastSession, backend()));
99}
100
101void BaseSessionService::ScheduleCommand(SessionCommand* command) {
102  DCHECK(command);
103  commands_since_reset_++;
104  pending_commands_.push_back(command);
105  StartSaveTimer();
106}
107
108void BaseSessionService::StartSaveTimer() {
109  // Don't start a timer when testing (profile == NULL or
110  // MessageLoop::current() is NULL).
111  if (base::MessageLoop::current() && profile() &&
112      !weak_factory_.HasWeakPtrs()) {
113    base::MessageLoop::current()->PostDelayedTask(
114        FROM_HERE,
115        base::Bind(&BaseSessionService::Save, weak_factory_.GetWeakPtr()),
116        base::TimeDelta::FromMilliseconds(kSaveDelayMS));
117  }
118}
119
120void BaseSessionService::Save() {
121  DCHECK(backend());
122
123  if (pending_commands_.empty())
124    return;
125
126  RunTaskOnBackendThread(
127      FROM_HERE,
128      base::Bind(&SessionBackend::AppendCommands, backend(),
129                 new std::vector<SessionCommand*>(pending_commands_),
130                 pending_reset_));
131
132  // Backend took ownership of commands.
133  pending_commands_.clear();
134
135  if (pending_reset_) {
136    commands_since_reset_ = 0;
137    pending_reset_ = false;
138  }
139}
140
141SessionCommand* BaseSessionService::CreateUpdateTabNavigationCommand(
142    SessionID::id_type command_id,
143    SessionID::id_type tab_id,
144    const sessions::SerializedNavigationEntry& navigation) {
145  // Use pickle to handle marshalling.
146  Pickle pickle;
147  pickle.WriteInt(tab_id);
148  // We only allow navigations up to 63k (which should be completely
149  // reasonable).
150  static const size_t max_state_size =
151      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
152  navigation.WriteToPickle(max_state_size, &pickle);
153  return new SessionCommand(command_id, pickle);
154}
155
156SessionCommand* BaseSessionService::CreateSetTabExtensionAppIDCommand(
157    SessionID::id_type command_id,
158    SessionID::id_type tab_id,
159    const std::string& extension_id) {
160  // Use pickle to handle marshalling.
161  Pickle pickle;
162  pickle.WriteInt(tab_id);
163
164  // Enforce a max for ids. They should never be anywhere near this size.
165  static const SessionCommand::size_type max_id_size =
166      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
167
168  int bytes_written = 0;
169
170  WriteStringToPickle(pickle, &bytes_written, max_id_size, extension_id);
171
172  return new SessionCommand(command_id, pickle);
173}
174
175SessionCommand* BaseSessionService::CreateSetTabUserAgentOverrideCommand(
176    SessionID::id_type command_id,
177    SessionID::id_type tab_id,
178    const std::string& user_agent_override) {
179  // Use pickle to handle marshalling.
180  Pickle pickle;
181  pickle.WriteInt(tab_id);
182
183  // Enforce a max for the user agent length.  They should never be anywhere
184  // near this size.
185  static const SessionCommand::size_type max_user_agent_size =
186      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
187
188  int bytes_written = 0;
189
190  WriteStringToPickle(pickle, &bytes_written, max_user_agent_size,
191      user_agent_override);
192
193  return new SessionCommand(command_id, pickle);
194}
195
196SessionCommand* BaseSessionService::CreateSetWindowAppNameCommand(
197    SessionID::id_type command_id,
198    SessionID::id_type window_id,
199    const std::string& app_name) {
200  // Use pickle to handle marshalling.
201  Pickle pickle;
202  pickle.WriteInt(window_id);
203
204  // Enforce a max for ids. They should never be anywhere near this size.
205  static const SessionCommand::size_type max_id_size =
206      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
207
208  int bytes_written = 0;
209
210  WriteStringToPickle(pickle, &bytes_written, max_id_size, app_name);
211
212  return new SessionCommand(command_id, pickle);
213}
214
215bool BaseSessionService::RestoreUpdateTabNavigationCommand(
216    const SessionCommand& command,
217    sessions::SerializedNavigationEntry* navigation,
218    SessionID::id_type* tab_id) {
219  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
220  if (!pickle.get())
221    return false;
222  PickleIterator iterator(*pickle);
223  return
224      pickle->ReadInt(&iterator, tab_id) &&
225      navigation->ReadFromPickle(&iterator);
226}
227
228bool BaseSessionService::RestoreSetTabExtensionAppIDCommand(
229    const SessionCommand& command,
230    SessionID::id_type* tab_id,
231    std::string* extension_app_id) {
232  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
233  if (!pickle.get())
234    return false;
235
236  PickleIterator iterator(*pickle);
237  return pickle->ReadInt(&iterator, tab_id) &&
238      pickle->ReadString(&iterator, extension_app_id);
239}
240
241bool BaseSessionService::RestoreSetTabUserAgentOverrideCommand(
242    const SessionCommand& command,
243    SessionID::id_type* tab_id,
244    std::string* user_agent_override) {
245  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
246  if (!pickle.get())
247    return false;
248
249  PickleIterator iterator(*pickle);
250  return pickle->ReadInt(&iterator, tab_id) &&
251      pickle->ReadString(&iterator, user_agent_override);
252}
253
254bool BaseSessionService::RestoreSetWindowAppNameCommand(
255    const SessionCommand& command,
256    SessionID::id_type* window_id,
257    std::string* app_name) {
258  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
259  if (!pickle.get())
260    return false;
261
262  PickleIterator iterator(*pickle);
263  return pickle->ReadInt(&iterator, window_id) &&
264      pickle->ReadString(&iterator, app_name);
265}
266
267bool BaseSessionService::ShouldTrackEntry(const GURL& url) {
268  // Blacklist chrome://quit and chrome://restart to avoid quit or restart
269  // loops.
270  return url.is_valid() && !(url.SchemeIs(content::kChromeUIScheme) &&
271                             (url.host() == chrome::kChromeUIQuitHost ||
272                              url.host() == chrome::kChromeUIRestartHost));
273}
274
275base::CancelableTaskTracker::TaskId
276BaseSessionService::ScheduleGetLastSessionCommands(
277    const InternalGetCommandsCallback& callback,
278    base::CancelableTaskTracker* tracker) {
279  base::CancelableTaskTracker::IsCanceledCallback is_canceled;
280  base::CancelableTaskTracker::TaskId id =
281      tracker->NewTrackedTaskId(&is_canceled);
282
283  InternalGetCommandsCallback run_if_not_canceled =
284      base::Bind(&RunIfNotCanceled, is_canceled, callback);
285
286  InternalGetCommandsCallback callback_runner =
287      base::Bind(&PostOrRunInternalGetCommandsCallback,
288                 base::MessageLoopProxy::current(), run_if_not_canceled);
289
290  RunTaskOnBackendThread(
291      FROM_HERE,
292      base::Bind(&SessionBackend::ReadLastSessionCommands, backend(),
293                 is_canceled, callback_runner));
294  return id;
295}
296
297void BaseSessionService::RunTaskOnBackendThread(
298    const tracked_objects::Location& from_here,
299    const base::Closure& task) {
300  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
301  base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
302  if (!pool->IsShutdownInProgress()) {
303    pool->PostSequencedWorkerTask(sequence_token_, from_here, task);
304  } else {
305    // Fall back to executing on the main thread if the sequence
306    // worker pool has been requested to shutdown (around shutdown
307    // time).
308    task.Run();
309  }
310}
311