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
~InternalGetCommandsRequest()19 BaseSessionService::InternalGetCommandsRequest::~InternalGetCommandsRequest() {
20 STLDeleteElements(&commands);
21 }
22
23 // BaseSessionService ---------------------------------------------------------
24
25 namespace {
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.
WriteStringToPickle(Pickle & pickle,int * bytes_written,int max_bytes,const std::string & str)30 void 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.
WriteString16ToPickle(Pickle & pickle,int * bytes_written,int max_bytes,const string16 & str)42 void 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.
57 static const int kSaveDelayMS = 2500;
58
59 // static
60 const int BaseSessionService::max_persist_navigation_count = 6;
61
BaseSessionService(SessionType type,Profile * profile,const FilePath & path)62 BaseSessionService::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
~BaseSessionService()84 BaseSessionService::~BaseSessionService() {
85 }
86
DeleteLastSession()87 void 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
ScheduleCommand(SessionCommand * command)96 void BaseSessionService::ScheduleCommand(SessionCommand* command) {
97 DCHECK(command);
98 commands_since_reset_++;
99 pending_commands_.push_back(command);
100 StartSaveTimer();
101 }
102
StartSaveTimer()103 void 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
Save()113 void 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
CreateUpdateTabNavigationCommand(SessionID::id_type command_id,SessionID::id_type tab_id,int index,const NavigationEntry & entry)137 SessionCommand* 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
CreateSetTabExtensionAppIDCommand(SessionID::id_type command_id,SessionID::id_type tab_id,const std::string & extension_id)183 SessionCommand* 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
RestoreUpdateTabNavigationCommand(const SessionCommand & command,TabNavigation * navigation,SessionID::id_type * tab_id)202 bool 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
RestoreSetTabExtensionAppIDCommand(const SessionCommand & command,SessionID::id_type * tab_id,std::string * extension_app_id)236 bool 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
ShouldTrackEntry(const NavigationEntry & entry)249 bool BaseSessionService::ShouldTrackEntry(const NavigationEntry& entry) {
250 return entry.virtual_url().is_valid();
251 }
252
ShouldTrackEntry(const TabNavigation & navigation)253 bool BaseSessionService::ShouldTrackEntry(const TabNavigation& navigation) {
254 return navigation.virtual_url().is_valid();
255 }
256
ScheduleGetLastSessionCommands(InternalGetCommandsRequest * request,CancelableRequestConsumerBase * consumer)257 BaseSessionService::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
271 BaseSessionService::Handle
ScheduleGetCurrentSessionCommands(InternalGetCommandsRequest * request,CancelableRequestConsumerBase * consumer)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