Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(688)

Side by Side Diff: chrome/browser/download/download_history.cc

Issue 10915180: Make DownloadHistory observe manager, items (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: @r166419 Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // DownloadHistory manages persisting DownloadItems to the history service by
6 // observing a single DownloadManager and all its DownloadItems using an
7 // AllDownloadItemNotifier.
8 //
9 // DownloadHistory decides whether and when to add items to, remove items from,
10 // and update items in the database. DownloadHistory uses DownloadHistoryData to
11 // store per-DownloadItem data such as its db_handle, whether the item is being
12 // added and waiting for its db_handle, and the last DownloadPersistentStoreInfo
13 // that was passed to the database. When the DownloadManager and its delegate
14 // (ChromeDownloadManagerDelegate) are initialized, DownloadHistory is created
15 // and queries the HistoryService. When the HistoryService calls back from
16 // QueryDownloads() to QueryCallback(), DownloadHistory uses
17 // DownloadManager::CreateDownloadItem() to inform DownloadManager of these
18 // persisted DownloadItems. CreateDownloadItem() internally calls
19 // OnDownloadCreated(), which normally adds items to the database, so
20 // QueryCallback() uses |loading_db_handle_| to disable adding these items to
21 // the database as it matches them up with their db_handles. If a download is
22 // removed via OnDownloadRemoved() while the item is still being added to the
23 // database, DownloadHistory uses |removed_while_adding_| to remember to remove
24 // the item when its ItemAdded() callback is called. All callbacks are bound
25 // with a weak pointer to DownloadHistory to prevent use-after-free bugs.
26 // ChromeDownloadManagerDelegate owns DownloadHistory, and deletes it in
27 // Shutdown(), which is called by DownloadManagerImpl::Shutdown() after all
28 // DownloadItems are destroyed.
29 //
30 // Strictly speaking, the weak pointers in the callbacks from the history system
31 // are redundant with the CancelableRequestConsumer.
32 // TODO(benjhayden) Use PostTaskAndReply with the weak pointers instead of
33 // CancelableRequestConsumer. This requires modifying the downloads-related
34 // portion of the HistoryService interface.
35
5 #include "chrome/browser/download/download_history.h" 36 #include "chrome/browser/download/download_history.h"
6 37
7 #include "base/logging.h" 38 #include "base/metrics/histogram.h"
8 #include "chrome/browser/download/download_crx_util.h" 39 #include "chrome/browser/download/download_crx_util.h"
9 #include "chrome/browser/history/history_marshaling.h" 40 #include "chrome/browser/history/download_database.h"
10 #include "chrome/browser/history/history_service_factory.h" 41 #include "chrome/browser/history/download_persistent_store_info.h"
11 #include "chrome/browser/profiles/profile.h" 42 #include "chrome/browser/history/history.h"
43 #include "content/public/browser/browser_thread.h"
12 #include "content/public/browser/download_item.h" 44 #include "content/public/browser/download_item.h"
13 #include "content/public/browser/download_persistent_store_info.h" 45 #include "content/public/browser/download_manager.h"
14 46
15 using content::DownloadItem; 47 namespace {
16 using content::DownloadPersistentStoreInfo; 48
17 49 // Per-DownloadItem data. This information does not belong inside DownloadItem,
18 DownloadHistory::DownloadHistory(Profile* profile) 50 // and keeping maps in DownloadHistory from DownloadItem to this information is
19 : profile_(profile), 51 // error-prone and complicated. Unfortunately, DownloadHistory::removing_*_ and
20 next_fake_db_handle_(DownloadItem::kUninitializedHandle - 1) { 52 // removed_while_adding_ cannot be moved into this class partly because
21 DCHECK(profile); 53 // DownloadHistoryData is destroyed when DownloadItems are destroyed, and we
22 } 54 // have no control over when DownloadItems are destroyed.
23 55 class DownloadHistoryData : public base::SupportsUserData::Data {
24 DownloadHistory::~DownloadHistory() {} 56 public:
25 57 static DownloadHistoryData* Get(content::DownloadItem* item) {
26 void DownloadHistory::GetNextId( 58 base::SupportsUserData::Data* data = item->GetUserData(kKey);
27 const HistoryService::DownloadNextIdCallback& callback) { 59 return (data == NULL) ? NULL :
28 HistoryService* hs = HistoryServiceFactory::GetForProfile( 60 static_cast<DownloadHistoryData*>(data);
29 profile_, Profile::EXPLICIT_ACCESS); 61 }
30 if (!hs) 62
31 return; 63 DownloadHistoryData(content::DownloadItem* item, int64 handle)
32 64 : is_adding_(false),
33 hs->GetNextDownloadId(&history_consumer_, callback); 65 db_handle_(handle),
34 } 66 info_(NULL) {
35 67 item->SetUserData(kKey, this);
36 void DownloadHistory::Load( 68 }
37 const HistoryService::DownloadQueryCallback& callback) { 69
38 HistoryService* hs = HistoryServiceFactory::GetForProfile( 70 virtual ~DownloadHistoryData() {
39 profile_, Profile::EXPLICIT_ACCESS); 71 }
40 if (!hs) 72
41 return; 73 // Whether this item is currently being added to the database.
42 74 bool is_adding() const { return is_adding_; }
43 hs->QueryDownloads(&history_consumer_, callback); 75 void set_is_adding(bool a) { is_adding_ = a; }
44 76
45 // This is the initial load, so do a cleanup of corrupt in-progress entries. 77 // Whether this item is already persisted in the database.
46 hs->CleanUpInProgressEntries(); 78 bool is_persisted() const {
47 } 79 return db_handle_ != history::DownloadDatabase::kUninitializedHandle;
48 80 }
49 void DownloadHistory::CheckVisitedReferrerBefore( 81
50 int32 download_id, 82 int64 db_handle() const { return db_handle_; }
51 const GURL& referrer_url, 83 void set_db_handle(int64 h) { db_handle_ = h; }
52 const VisitedBeforeDoneCallback& callback) { 84
53 if (referrer_url.is_valid()) { 85 // This allows DownloadHistory::OnDownloadUpdated() to see what changed in a
54 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( 86 // DownloadItem if anything, in order to prevent writing to the database
55 profile_, Profile::EXPLICIT_ACCESS); 87 // unnecessarily. It is nullified when the item is no longer in progress in
56 if (hs) { 88 // order to save memory.
57 HistoryService::Handle handle = 89 DownloadPersistentStoreInfo* info() { return info_.get(); }
58 hs->GetVisibleVisitCountToHost(referrer_url, &history_consumer_, 90 void set_info(const DownloadPersistentStoreInfo& i) {
59 base::Bind(&DownloadHistory::OnGotVisitCountToHost, 91 info_.reset(new DownloadPersistentStoreInfo(i));
60 base::Unretained(this))); 92 }
61 visited_before_requests_[handle] = callback; 93 void clear_info() {
62 return; 94 info_.reset();
95 }
96
97 private:
98 static const char kKey[];
99
100 bool is_adding_;
101 int64 db_handle_;
102 scoped_ptr<DownloadPersistentStoreInfo> info_;
103
104 DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData);
105 };
106
107 const char DownloadHistoryData::kKey[] =
108 "DownloadItem DownloadHistoryData";
109
110 DownloadPersistentStoreInfo GetPersistentStoreInfo(
111 content::DownloadItem* item) {
112 DownloadHistoryData* data = DownloadHistoryData::Get(item);
113 return DownloadPersistentStoreInfo(
114 item->GetFullPath(),
115 item->GetURL(),
116 item->GetReferrerUrl(),
117 item->GetStartTime(),
118 item->GetEndTime(),
119 item->GetReceivedBytes(),
120 item->GetTotalBytes(),
121 item->GetState(),
122 ((data != NULL) ? data->db_handle()
123 : history::DownloadDatabase::kUninitializedHandle),
124 item->GetOpened());
125 }
126
127 bool ShouldUpdateHistory(const DownloadPersistentStoreInfo* previous,
128 const DownloadPersistentStoreInfo& current) {
129 // Ignore url, referrer, start_time, db_handle, which don't change.
130 return ((previous == NULL) ||
131 (previous->path != current.path) ||
132 (previous->end_time != current.end_time) ||
133 (previous->received_bytes != current.received_bytes) ||
134 (previous->total_bytes != current.total_bytes) ||
135 (previous->state != current.state) ||
136 (previous->opened != current.opened));
137 }
138
139 typedef std::vector<DownloadPersistentStoreInfo> InfoVector;
140
141 } // anonymous namespace
142
143 DownloadHistory::Observer::Observer() {}
144 DownloadHistory::Observer::~Observer() {}
145
146 bool DownloadHistory::IsPersisted(content::DownloadItem* item) {
147 DownloadHistoryData* data = DownloadHistoryData::Get(item);
148 return data && data->is_persisted();
149 }
150
151 DownloadHistory::DownloadHistory(
152 content::DownloadManager* manager,
153 HistoryService* history)
154 : ALLOW_THIS_IN_INITIALIZER_LIST(notifier_(manager, this)),
155 history_(history),
156 loading_db_handle_(history::DownloadDatabase::kUninitializedHandle),
157 history_size_(0),
158 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
159 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
160 content::DownloadManager::DownloadVector items;
161 notifier_.GetManager()->GetAllDownloads(&items);
162 for (content::DownloadManager::DownloadVector::const_iterator
163 it = items.begin(); it != items.end(); ++it) {
164 OnDownloadCreated(notifier_.GetManager(), *it);
165 }
166 history_->QueryDownloads(&history_consumer_, base::Bind(
167 &DownloadHistory::QueryCallback, weak_ptr_factory_.GetWeakPtr()));
168 }
169
170 DownloadHistory::~DownloadHistory() {
171 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
172 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed());
173 observers_.Clear();
174 }
175
176 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) {
177 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
178 observers_.AddObserver(observer);
179 }
180
181 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) {
182 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
183 observers_.RemoveObserver(observer);
184 }
185
186 void DownloadHistory::QueryCallback(InfoVector* infos) {
187 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
188 // ManagerGoingDown() may have happened before the history loaded.
189 if (!notifier_.GetManager())
190 return;
191 for (InfoVector::const_iterator it = infos->begin();
192 it != infos->end(); ++it) {
193 // OnDownloadCreated() is called inside DM::CreateDownloadItem(), so set
194 // loading_db_handle_ to match up the created item with its db_handle. All
195 // methods run on the UI thread and CreateDownloadItem() is synchronous.
196 loading_db_handle_ = it->db_handle;
197 content::DownloadItem* download_item =
198 notifier_.GetManager()->CreateDownloadItem(
199 it->path,
200 it->url,
201 it->referrer_url,
202 it->start_time,
203 it->end_time,
204 it->received_bytes,
205 it->total_bytes,
206 it->state,
207 it->opened);
208 DownloadHistoryData* data = DownloadHistoryData::Get(download_item);
209
210 // If this DCHECK fails, then you probably added an Observer that
211 // synchronously creates a DownloadItem in response to
212 // DownloadManager::OnDownloadCreated(), and your observer runs before
213 // DownloadHistory, and DownloadManager creates items synchronously. Just
214 // bounce your DownloadItem creation off the message loop to flush
215 // DownloadHistory::OnDownloadCreated.
216 DCHECK_EQ(it->db_handle, data->db_handle());
217 ++history_size_;
218 }
219 notifier_.GetManager()->CheckForHistoryFilesRemoval();
220 }
221
222 void DownloadHistory::MaybeAddToHistory(content::DownloadItem* item) {
223 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
224
225 int32 download_id = item->GetId();
226 DownloadHistoryData* data = DownloadHistoryData::Get(item);
227 bool removing = (removing_handles_.find(data->db_handle()) !=
228 removing_handles_.end());
229
230 // TODO(benjhayden): Remove IsTemporary().
231 if (download_crx_util::IsExtensionDownload(*item) ||
232 item->IsTemporary() ||
233 data->is_adding() ||
234 data->is_persisted() ||
235 removing)
236 return;
237
238 data->set_is_adding(true);
239 if (data->info() == NULL) {
240 // Keep the info here regardless of whether the item is in progress so that,
241 // when ItemAdded() calls OnDownloadUpdated(), it can decide whether to
242 // Update the db and/or clear the info.
243 data->set_info(GetPersistentStoreInfo(item));
244 }
245
246 history_->CreateDownload(*data->info(), &history_consumer_, base::Bind(
247 &DownloadHistory::ItemAdded, weak_ptr_factory_.GetWeakPtr(),
248 download_id));
249 }
250
251 void DownloadHistory::ItemAdded(int32 download_id, int64 db_handle) {
252 if (removed_while_adding_.find(download_id) !=
253 removed_while_adding_.end()) {
254 removed_while_adding_.erase(download_id);
255 ScheduleRemoveDownload(download_id, db_handle);
256 return;
257 }
258
259 if (!notifier_.GetManager())
260 return;
261
262 content::DownloadItem* item = notifier_.GetManager()->GetDownload(
263 download_id);
264 if (!item) {
265 // This item will have called OnDownloadDestroyed(). If the item should
266 // have been removed from history, then it would have also called
267 // OnDownloadRemoved(), which would have put |download_id| in
268 // removed_while_adding_, handled above.
269 return;
270 }
271
272 DownloadHistoryData* data = DownloadHistoryData::Get(item);
273 data->set_is_adding(false);
274
275 // The sql INSERT statement failed. Avoid an infinite loop: don't
276 // automatically retry. Retry adding the next time the item is updated by
277 // unsetting is_adding.
278 if (db_handle == history::DownloadDatabase::kUninitializedHandle) {
279 DVLOG(20) << __FUNCTION__ << " INSERT failed id=" << download_id;
280 return;
281 }
282
283 data->set_db_handle(db_handle);
284
285 // Send to observers the actual DownloadPersistentStoreInfo that was sent to
286 // the db, plus the db_handle, instead of completely regenerating the
287 // DownloadPersistentStoreInfo, in order to accurately reflect the contents of
288 // the database.
289 data->info()->db_handle = db_handle;
290 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
291 item, *data->info()));
292
293 UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2",
294 history_size_,
295 0/*min*/,
296 (1 << 23)/*max*/,
297 (1 << 7)/*num_buckets*/);
298 ++history_size_;
299
300 // In case the item changed or became temporary while it was being added.
301 // Don't just update all of the item's observers because we're the only
302 // observer that can also see db_handle, which is the only thing that
303 // ItemAdded changed.
304 OnDownloadUpdated(notifier_.GetManager(), item);
305 }
306
307 void DownloadHistory::OnDownloadCreated(
308 content::DownloadManager* manager, content::DownloadItem* item) {
309 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
310
311 // All downloads should pass through OnDownloadCreated exactly once.
312 CHECK(!DownloadHistoryData::Get(item));
313 DownloadHistoryData* data = new DownloadHistoryData(item, loading_db_handle_);
314 loading_db_handle_ = history::DownloadDatabase::kUninitializedHandle;
315 if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
316 data->set_info(GetPersistentStoreInfo(item));
317 }
318 MaybeAddToHistory(item);
319 }
320
321 void DownloadHistory::OnDownloadUpdated(
322 content::DownloadManager* manager, content::DownloadItem* item) {
323 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
324
325 DownloadHistoryData* data = DownloadHistoryData::Get(item);
326 if (!data->is_persisted()) {
327 MaybeAddToHistory(item);
328 return;
329 }
330 if (item->IsTemporary()) {
331 OnDownloadRemoved(notifier_.GetManager(), item);
332 return;
333 }
334
335 // TODO(asanka): Persist GetTargetFilePath() as well.
336 DownloadPersistentStoreInfo current_info(GetPersistentStoreInfo(item));
337 bool should_update = ShouldUpdateHistory(data->info(), current_info);
338 UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate",
339 should_update, 2);
340 if (should_update) {
341 history_->UpdateDownload(current_info);
342 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
343 item, current_info));
344 }
345 if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
346 data->set_info(current_info);
347 } else {
348 data->clear_info();
349 }
350 }
351
352 void DownloadHistory::OnDownloadOpened(
353 content::DownloadManager* manager, content::DownloadItem* item) {
354 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
355 DownloadHistoryData* data = DownloadHistoryData::Get(item);
356 if (!data->is_persisted()) {
357 MaybeAddToHistory(item);
358 return;
359 }
360 if (item->IsTemporary()) {
361 OnDownloadRemoved(manager, item);
362 return;
363 }
364 // Downloads may be opened after they are completed, so don't rely on
365 // DownloadHistoryData::info().
366
367 DownloadPersistentStoreInfo current_info(GetPersistentStoreInfo(item));
368 history_->UpdateDownload(current_info);
369 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(item, current_info));
370 }
371
372 void DownloadHistory::OnDownloadRemoved(
373 content::DownloadManager* manager, content::DownloadItem* item) {
374 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
375
376 DownloadHistoryData* data = DownloadHistoryData::Get(item);
377 if (!data->is_persisted()) {
378 if (data->is_adding()) {
379 // ScheduleRemoveDownload will be called when history_ calls ItemAdded().
380 removed_while_adding_.insert(item->GetId());
63 } 381 }
64 } 382 return;
65 callback.Run(false); 383 }
66 } 384 ScheduleRemoveDownload(item->GetId(), data->db_handle());
67 385 data->set_db_handle(history::DownloadDatabase::kUninitializedHandle);
68 void DownloadHistory::AddEntry( 386 // ItemAdded increments history_size_ only if the item wasn't
69 DownloadItem* download_item, 387 // removed_while_adding_, so the next line does not belong in
70 const HistoryService::DownloadCreateCallback& callback) { 388 // ScheduleRemoveDownload().
71 DCHECK(download_item); 389 --history_size_;
72 // Do not store the download in the history database for a few special cases: 390 }
73 // - incognito mode (that is the point of this mode) 391
74 // - extensions (users don't think of extension installation as 'downloading') 392 void DownloadHistory::ScheduleRemoveDownload(
75 // - temporary download, like in drag-and-drop 393 int32 download_id, int64 db_handle) {
76 // - history service is not available (e.g. in tests) 394 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
77 // We have to make sure that these handles don't collide with normal db 395 if (db_handle == history::DownloadDatabase::kUninitializedHandle)
78 // handles, so we use a negative value. Eventually, they could overlap, but 396 return;
79 // you'd have to do enough downloading that your ISP would likely stab you in 397
80 // the neck first. YMMV. 398 // For database efficiency, batch removals together if they happen all at
81 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists( 399 // once.
82 profile_, Profile::EXPLICIT_ACCESS); 400 if (removing_handles_.empty()) {
83 if (download_crx_util::IsExtensionDownload(*download_item) || 401 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
84 download_item->IsTemporary() || !hs) { 402 base::Bind(&DownloadHistory::RemoveDownloadsBatch,
85 callback.Run(download_item->GetId(), GetNextFakeDbHandle()); 403 weak_ptr_factory_.GetWeakPtr()));
86 return; 404 }
87 } 405 removing_handles_.insert(db_handle);
88 406 removing_ids_.insert(download_id);
89 int32 id = download_item->GetId(); 407 }
90 DownloadPersistentStoreInfo history_info = 408
91 download_item->GetPersistentStoreInfo(); 409 void DownloadHistory::RemoveDownloadsBatch() {
92 hs->CreateDownload(id, history_info, &history_consumer_, callback); 410 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
93 } 411 HandleSet remove_handles;
94 412 IdSet remove_ids;
95 void DownloadHistory::UpdateEntry(DownloadItem* download_item) { 413 removing_handles_.swap(remove_handles);
96 // Don't store info in the database if the download was initiated while in 414 removing_ids_.swap(remove_ids);
97 // incognito mode or if it hasn't been initialized in our database table. 415 history_->RemoveDownloads(remove_handles);
98 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle) 416 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadsRemoved(remove_ids));
99 return; 417 }
100
101 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists(
102 profile_, Profile::EXPLICIT_ACCESS);
103 if (!hs)
104 return;
105 hs->UpdateDownload(download_item->GetPersistentStoreInfo());
106 }
107
108 void DownloadHistory::UpdateDownloadPath(DownloadItem* download_item,
109 const FilePath& new_path) {
110 // No update necessary if the download was initiated while in incognito mode.
111 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle)
112 return;
113
114 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists(
115 profile_, Profile::EXPLICIT_ACCESS);
116 if (hs)
117 hs->UpdateDownloadPath(new_path, download_item->GetDbHandle());
118 }
119
120 void DownloadHistory::RemoveEntry(DownloadItem* download_item) {
121 // No update necessary if the download was initiated while in incognito mode.
122 if (download_item->GetDbHandle() <= DownloadItem::kUninitializedHandle)
123 return;
124
125 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists(
126 profile_, Profile::EXPLICIT_ACCESS);
127 if (hs)
128 hs->RemoveDownload(download_item->GetDbHandle());
129 }
130
131 void DownloadHistory::RemoveEntriesBetween(const base::Time remove_begin,
132 const base::Time remove_end) {
133 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists(
134 profile_, Profile::EXPLICIT_ACCESS);
135 if (hs)
136 hs->RemoveDownloadsBetween(remove_begin, remove_end);
137 }
138
139 int64 DownloadHistory::GetNextFakeDbHandle() {
140 return next_fake_db_handle_--;
141 }
142
143 void DownloadHistory::OnGotVisitCountToHost(HistoryService::Handle handle,
144 bool found_visits,
145 int count,
146 base::Time first_visit) {
147 VisitedBeforeRequestsMap::iterator request =
148 visited_before_requests_.find(handle);
149 DCHECK(request != visited_before_requests_.end());
150 VisitedBeforeDoneCallback callback = request->second;
151 visited_before_requests_.erase(request);
152 callback.Run(found_visits && count &&
153 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight()));
154 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698