base_session_service.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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      ALLOW_THIS_IN_INITIALIZER_LIST(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 TabNavigation& navigation) {
152  // Use pickle to handle marshalling.
153  Pickle pickle;
154  pickle.WriteInt(tab_id);
155  navigation.WriteToPickle(&pickle);
156  return new SessionCommand(command_id, pickle);
157}
158
159SessionCommand* BaseSessionService::CreateSetTabExtensionAppIDCommand(
160    SessionID::id_type command_id,
161    SessionID::id_type tab_id,
162    const std::string& extension_id) {
163  // Use pickle to handle marshalling.
164  Pickle pickle;
165  pickle.WriteInt(tab_id);
166
167  // Enforce a max for ids. They should never be anywhere near this size.
168  static const SessionCommand::size_type max_id_size =
169      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
170
171  int bytes_written = 0;
172
173  WriteStringToPickle(pickle, &bytes_written, max_id_size, extension_id);
174
175  return new SessionCommand(command_id, pickle);
176}
177
178SessionCommand* BaseSessionService::CreateSetTabUserAgentOverrideCommand(
179    SessionID::id_type command_id,
180    SessionID::id_type tab_id,
181    const std::string& user_agent_override) {
182  // Use pickle to handle marshalling.
183  Pickle pickle;
184  pickle.WriteInt(tab_id);
185
186  // Enforce a max for the user agent length.  They should never be anywhere
187  // near this size.
188  static const SessionCommand::size_type max_user_agent_size =
189      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
190
191  int bytes_written = 0;
192
193  WriteStringToPickle(pickle, &bytes_written, max_user_agent_size,
194      user_agent_override);
195
196  return new SessionCommand(command_id, pickle);
197}
198
199SessionCommand* BaseSessionService::CreateSetWindowAppNameCommand(
200    SessionID::id_type command_id,
201    SessionID::id_type window_id,
202    const std::string& app_name) {
203  // Use pickle to handle marshalling.
204  Pickle pickle;
205  pickle.WriteInt(window_id);
206
207  // Enforce a max for ids. They should never be anywhere near this size.
208  static const SessionCommand::size_type max_id_size =
209      std::numeric_limits<SessionCommand::size_type>::max() - 1024;
210
211  int bytes_written = 0;
212
213  WriteStringToPickle(pickle, &bytes_written, max_id_size, app_name);
214
215  return new SessionCommand(command_id, pickle);
216}
217
218bool BaseSessionService::RestoreUpdateTabNavigationCommand(
219    const SessionCommand& command,
220    TabNavigation* navigation,
221    SessionID::id_type* tab_id) {
222  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
223  if (!pickle.get())
224    return false;
225  PickleIterator iterator(*pickle);
226  return
227      pickle->ReadInt(&iterator, tab_id) &&
228      navigation->ReadFromPickle(&iterator);
229}
230
231bool BaseSessionService::RestoreSetTabExtensionAppIDCommand(
232    const SessionCommand& command,
233    SessionID::id_type* tab_id,
234    std::string* extension_app_id) {
235  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
236  if (!pickle.get())
237    return false;
238
239  PickleIterator iterator(*pickle);
240  return pickle->ReadInt(&iterator, tab_id) &&
241      pickle->ReadString(&iterator, extension_app_id);
242}
243
244bool BaseSessionService::RestoreSetTabUserAgentOverrideCommand(
245    const SessionCommand& command,
246    SessionID::id_type* tab_id,
247    std::string* user_agent_override) {
248  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
249  if (!pickle.get())
250    return false;
251
252  PickleIterator iterator(*pickle);
253  return pickle->ReadInt(&iterator, tab_id) &&
254      pickle->ReadString(&iterator, user_agent_override);
255}
256
257bool BaseSessionService::RestoreSetWindowAppNameCommand(
258    const SessionCommand& command,
259    SessionID::id_type* window_id,
260    std::string* app_name) {
261  scoped_ptr<Pickle> pickle(command.PayloadAsPickle());
262  if (!pickle.get())
263    return false;
264
265  PickleIterator iterator(*pickle);
266  return pickle->ReadInt(&iterator, window_id) &&
267      pickle->ReadString(&iterator, app_name);
268}
269
270bool BaseSessionService::ShouldTrackEntry(const GURL& url) {
271  return url.is_valid();
272}
273
274CancelableTaskTracker::TaskId
275    BaseSessionService::ScheduleGetLastSessionCommands(
276    const InternalGetCommandsCallback& callback,
277    CancelableTaskTracker* tracker) {
278  CancelableTaskTracker::IsCanceledCallback is_canceled;
279  CancelableTaskTracker::TaskId id = tracker->NewTrackedTaskId(&is_canceled);
280
281  InternalGetCommandsCallback run_if_not_canceled =
282      base::Bind(&RunIfNotCanceled, is_canceled, callback);
283
284  InternalGetCommandsCallback callback_runner =
285      base::Bind(&PostOrRunInternalGetCommandsCallback,
286                 base::MessageLoopProxy::current(), run_if_not_canceled);
287
288  RunTaskOnBackendThread(
289      FROM_HERE,
290      base::Bind(&SessionBackend::ReadLastSessionCommands, backend(),
291                 is_canceled, callback_runner));
292  return id;
293}
294
295bool BaseSessionService::RunTaskOnBackendThread(
296    const tracked_objects::Location& from_here,
297    const base::Closure& task) {
298  if (profile_ && BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) {
299    return BrowserThread::PostTask(BrowserThread::FILE, from_here, task);
300  } else {
301    // Fall back to executing on the main thread if the file thread
302    // has gone away (around shutdown time) or if we're running as
303    // part of a unit test that does not set profile_.
304    task.Run();
305    return true;
306  }
307}
308
309bool BaseSessionService::RunningInProduction() const {
310  return profile_ && BrowserThread::IsMessageLoopValid(BrowserThread::FILE);
311}
312