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

Side by Side Diff: chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.cc

Issue 663023007: Include high-fidelity metadata about a download in incident reports. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git/+/master
Patch Set: added DCHECK Created 6 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
OLDNEW
(Empty)
1 // Copyright 2014 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/safe_browsing/incident_reporting/download_metadata_mana ger.h"
6
7 #include <list>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/files/file.h"
12 #include "base/files/file_util.h"
13 #include "base/files/important_file_writer.h"
14 #include "base/location.h"
15 #include "base/metrics/histogram.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/threading/sequenced_worker_pool.h"
18 #include "chrome/common/safe_browsing/csd.pb.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/download_item.h"
21
22 namespace safe_browsing {
23
24 namespace {
25
26 // Histogram bucket values for metadata read operations. Do not reorder.
27 enum MetadataReadResult {
28 READ_SUCCESS = 0,
29 OPEN_FAILURE = 1,
30 NOT_FOUND = 2,
31 GET_INFO_FAILURE = 3,
32 FILE_TOO_BIG = 4,
33 READ_FAILURE = 5,
34 PARSE_FAILURE = 6,
35 MALFORMED_DATA = 7,
36 NUM_READ_RESULTS
37 };
38
39 // Histogram bucket values for metadata write operations. Do not reorder.
40 enum MetadataWriteResult {
41 WRITE_SUCCESS = 0,
42 SERIALIZATION_FAILURE = 1,
43 WRITE_FAILURE = 2,
44 NUM_WRITE_RESULTS
45 };
46
47 // The name of the metadata file in the profile directory.
48 const base::FilePath::CharType kDownloadMetadataBasename[] =
49 FILE_PATH_LITERAL("DownloadMetadata");
50
51 // Returns the path to the metadata file for |browser_context|.
52 base::FilePath GetMetadataPath(content::BrowserContext* browser_context) {
53 return browser_context->GetPath().Append(kDownloadMetadataBasename);
54 }
55
56 // Returns true if |metadata| appears to be valid.
57 bool MetadataIsValid(const DownloadMetadata& metadata) {
58 return (metadata.has_download_id() &&
59 metadata.has_download() &&
60 metadata.download().has_download() &&
61 metadata.download().download().has_url());
62 }
63
64 // Reads and parses a DownloadMetadata message from |metadata_path| into
65 // |metadata|.
66 void ReadMetadataOnWorkerPool(const base::FilePath& metadata_path,
67 DownloadMetadata* metadata) {
68 using base::File;
69 DCHECK(metadata);
70 MetadataReadResult result = NUM_READ_RESULTS;
71 File metadata_file(metadata_path, File::FLAG_OPEN | File::FLAG_READ);
72 if (metadata_file.IsValid()) {
73 base::File::Info info;
74 if (metadata_file.GetInfo(&info)) {
75 if (info.size <= INT_MAX) {
76 const int size = static_cast<int>(info.size);
77 scoped_ptr<char[]> file_data(new char[info.size]);
78 if (metadata_file.Read(0, file_data.get(), size)) {
79 if (!metadata->ParseFromArray(file_data.get(), size))
80 result = PARSE_FAILURE;
81 else if (!MetadataIsValid(*metadata))
82 result = MALFORMED_DATA;
83 else
84 result = READ_SUCCESS;
85 } else {
86 result = READ_FAILURE;
87 }
88 } else {
89 result = FILE_TOO_BIG;
90 }
91 } else {
92 result = GET_INFO_FAILURE;
93 }
94 } else if (metadata_file.error_details() != File::FILE_ERROR_NOT_FOUND) {
95 result = OPEN_FAILURE;
96 } else {
97 result = NOT_FOUND;
98 }
99 if (result != READ_SUCCESS)
100 metadata->Clear();
101 UMA_HISTOGRAM_ENUMERATION(
102 "SBIRS.DownloadMetadata.ReadResult", result, NUM_READ_RESULTS);
103 }
104
105 // Writes |download_metadata| to |metadata_path|.
106 void WriteMetadataOnWorkerPool(const base::FilePath& metadata_path,
107 DownloadMetadata* download_metadata) {
108 MetadataWriteResult result = NUM_WRITE_RESULTS;
109 std::string file_data;
110 if (download_metadata->SerializeToString(&file_data)) {
111 result =
112 base::ImportantFileWriter::WriteFileAtomically(metadata_path, file_data)
113 ? WRITE_SUCCESS
114 : WRITE_FAILURE;
115 } else {
116 result = SERIALIZATION_FAILURE;
117 }
118 UMA_HISTOGRAM_ENUMERATION(
119 "SBIRS.DownloadMetadata.WriteResult", result, NUM_WRITE_RESULTS);
120 }
121
122 // Deletes |metadata_path|.
123 void DeleteMetadataOnWorkerPool(const base::FilePath& metadata_path) {
124 bool success = base::DeleteFile(metadata_path, false /* not recursive */);
125 UMA_HISTOGRAM_BOOLEAN("SBIRS.DownloadMetadata.DeleteSuccess", success);
126 }
127
128 // Runs |callback| with the DownloadDetails in |download_metadata|.
129 void ReturnResults(
130 const DownloadMetadataManager::GetDownloadDetailsCallback& callback,
131 scoped_ptr<DownloadMetadata> download_metadata) {
132 if (!download_metadata->has_download_id())
133 callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
134 else
135 callback.Run(make_scoped_ptr(download_metadata->release_download()).Pass());
136 }
137
138 } // namespace
139
140 // Applies operations to the profile's persistent DownloadMetadata as they occur
141 // on its corresponding download item. An instance can be in one of three
142 // states: waiting for metatada load, waiting for metadata to load after its
143 // corresponding DownloadManager has gone down, and not waiting for metadata to
144 // load. While it is waiting for metadata to load, it observes all download
145 // items and records operations on them. Once the metadata is ready, recorded
146 // operations are applied and observers are removed from all items but the one
147 // corresponding to the metadata (if it exists). The instance continues to
148 // observe its related item, and applies operations on it accordingly. While
149 // waiting for metadata to load, an instance also tracks callbacks to be run to
150 // provide consumers with persisted details of a download.
151 class DownloadMetadataManager::ManagerContext
152 : public content::DownloadItem::Observer {
153 public:
154 ManagerContext(const scoped_refptr<base::SequencedTaskRunner>& read_runner,
155 const scoped_refptr<base::SequencedTaskRunner>& write_runner,
156 content::DownloadManager* download_manager);
157
158 // Detaches this context from its owner. The owner must not access the context
159 // following this call. The context will be deleted immediately if it is not
160 // waiting for a metadata load with either recorded operations or pending
161 // callbacks.
162 void Detach();
163
164 // Notifies the context that |download| has been added to its manager.
165 void OnDownloadCreated(content::DownloadItem* download);
166
167 // Sets |request| as the relevant metadata to persist for |download|.
168 void SetRequest(content::DownloadItem* download,
169 const ClientDownloadRequest& request);
170
171 // Removes metadata for the context from memory and posts a task in the worker
172 // pool to delete it on disk.
173 void RemoveMetadata();
174
175 // Gets the persisted DownloadDetails. |callback| will be run immediately if
176 // the data is available. Otherwise, it will be run later on the caller's
177 // thread.
178 void GetDownloadDetails(const GetDownloadDetailsCallback& callback);
179
180 protected:
181 // content::DownloadItem::Observer methods.
182 void OnDownloadOpened(content::DownloadItem* download) override;
183 void OnDownloadRemoved(content::DownloadItem* download) override;
184 void OnDownloadDestroyed(content::DownloadItem* download) override;
185
186 private:
187 enum State {
188 // The context is waiting for the metadata file to be loaded.
189 WAITING_FOR_LOAD,
190
191 // The context is waiting for the metadata file to be loaded and its
192 // corresponding DownloadManager has gone away.
193 DETACHED_WAIT,
194
195 // The context has loaded the metadata file.
196 LOAD_COMPLETE,
197 };
198
199 struct ItemData {
200 ItemData() : item(), removed() {}
201 // null if the download has been destroyed. If non-null, the item is being
202 // observed.
203 content::DownloadItem* item;
204 base::Time last_opened_time;
205 bool removed;
206 };
207
208 // A mapping of download IDs to their corresponding data.
209 typedef std::map<uint32_t, ItemData> ItemDataMap;
210
211 virtual ~ManagerContext();
212
213 // Clears the |pending_items_| mapping, removing observers in the process.
214 void ClearPendingItems();
215
216 // Runs all |get_details_callbacks_| with the current metadata.
217 void RunCallbacks();
218
219 // Returns the id of the download corresponding to the loaded metadata, or
220 // kInvalidId if metadata has not finished loading or is not present.
221 uint32_t GetDownloadId() const;
222
223 // Posts a task in the worker pool to read the metadata from disk.
224 void ReadMetadata();
225
226 // A callback run on the main thread with the results from reading the
227 // metadata file from disk.
228 void OnMetadataReady(scoped_ptr<DownloadMetadata> download_metadata);
229
230 // Updates the last opened time in the metadata and writes it to disk.
231 void UpdateLastOpenedTime(const base::Time& last_opened_time);
232
233 // Posts a task in the worker pool to write the metadata to disk.
234 void WriteMetadata();
235
236 // A task runner to which read tasks are posted.
237 scoped_refptr<base::SequencedTaskRunner> read_runner_;
238
239 // A task runner to which write tasks are posted.
240 scoped_refptr<base::SequencedTaskRunner> write_runner_;
241
242 // The path to the metadata file for this context.
243 base::FilePath metadata_path_;
244
245 // When not LOAD_COMPLETE, the context is waiting for a pending read operation
246 // to complete. While this is the case, all added download items are observed
247 // and events are temporarily recorded in |pending_items_|. Once the read
248 // completes, pending operations for the item correponding to the metadata
249 // file are applied to the file and all other recorded data are dropped (and
250 // corresponding observers are removed). Queued GetDownloadDetailsCallbacks
251 // are run upon read completion as well. The context is moved to the
252 // DETACHED_WAIT state if the corresponding DownloadManager goes away while a
253 // read operation is outstanding. When the read subsequently completes, the
254 // context is destroyed after the processing described above is performed.
255 State state_;
256
257 // The current metadata for the context. May be supplied either by reading
258 // from the file or by having been set via |SetRequest|.
259 scoped_ptr<DownloadMetadata> download_metadata_;
260
261 // The operation data that accumulates for added download items while the
262 // metadata file is being read.
263 ItemDataMap pending_items_;
264
265 // The download item corresponding to the download_metadata_. When non-null,
266 // the context observes events on this item only.
267 content::DownloadItem* item_;
268
269 // Pending callbacks in response to GetDownloadDetails. The callbacks are run
270 // in order when a pending read operation completes.
271 std::list<GetDownloadDetailsCallback> get_details_callbacks_;
272
273 base::WeakPtrFactory<ManagerContext> weak_factory_;
274
275 DISALLOW_COPY_AND_ASSIGN(ManagerContext);
276 };
277
278 DownloadMetadataManager::DownloadMetadataManager(
279 const scoped_refptr<base::SequencedWorkerPool>& worker_pool) {
280 base::SequencedWorkerPool::SequenceToken token(
281 worker_pool->GetSequenceToken());
282 // Do not block shutdown on reads since nothing will come of it.
283 read_runner_ = worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
284 token, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
285 // Block shutdown on writes only if they've already begun.
286 write_runner_ = worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
287 token, base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
288 }
289
290 DownloadMetadataManager::DownloadMetadataManager(
291 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
292 : read_runner_(task_runner), write_runner_(task_runner) {
293 }
294
295 DownloadMetadataManager::~DownloadMetadataManager() {
296 // All download managers must have gone down prior to this.
297 DCHECK(contexts_.empty());
298 }
299
300 void DownloadMetadataManager::AddDownloadManager(
301 content::DownloadManager* download_manager) {
302 DCHECK_EQ(contexts_.count(download_manager), 0U);
303 download_manager->AddObserver(this);
304 contexts_[download_manager] =
305 new ManagerContext(read_runner_, write_runner_, download_manager);
306 }
307
308 void DownloadMetadataManager::SetRequest(content::DownloadItem* item,
309 const ClientDownloadRequest* request) {
310 content::DownloadManager* download_manager =
311 GetDownloadManagerForBrowserContext(item->GetBrowserContext());
312 DCHECK_EQ(contexts_.count(download_manager), 1U);
313 if (request)
314 contexts_[download_manager]->SetRequest(item, *request);
315 else
316 contexts_[download_manager]->RemoveMetadata();
317 }
318
319 void DownloadMetadataManager::GetDownloadDetails(
320 content::BrowserContext* browser_context,
321 const GetDownloadDetailsCallback& callback) {
322 DCHECK(browser_context);
323 // The DownloadManager for |browser_context| may not have been created yet. In
324 // this case, asking for it would cause history to load in the background and
325 // wouldn't really help much. Instead, scan the contexts to see if one belongs
326 // to |browser_context|. If one is not found, read the metadata and return it.
327 scoped_ptr<ClientIncidentReport_DownloadDetails> download_details;
328 for (const auto& value : contexts_) {
329 if (value.first->GetBrowserContext() == browser_context) {
330 value.second->GetDownloadDetails(callback);
331 return;
332 }
333 }
334
335 // Fire off a task to load the details and return them to the caller.
336 DownloadMetadata* metadata = new DownloadMetadata();
337 read_runner_->PostTaskAndReply(
338 FROM_HERE,
339 base::Bind(&ReadMetadataOnWorkerPool,
340 GetMetadataPath(browser_context),
341 metadata),
342 base::Bind(
343 &ReturnResults, callback, base::Passed(make_scoped_ptr(metadata))));
344 }
345
346 content::DownloadManager*
347 DownloadMetadataManager::GetDownloadManagerForBrowserContext(
348 content::BrowserContext* context) {
349 return content::BrowserContext::GetDownloadManager(context);
350 }
351
352 void DownloadMetadataManager::OnDownloadCreated(
353 content::DownloadManager* download_manager,
354 content::DownloadItem* item) {
355 DCHECK_EQ(contexts_.count(download_manager), 1U);
356 contexts_[download_manager]->OnDownloadCreated(item);
357 }
358
359 void DownloadMetadataManager::ManagerGoingDown(
360 content::DownloadManager* download_manager) {
361 DCHECK_EQ(contexts_.count(download_manager), 1U);
362 auto iter = contexts_.find(download_manager);
363 iter->second->Detach();
364 contexts_.erase(iter);
365 }
366
367 DownloadMetadataManager::ManagerContext::ManagerContext(
368 const scoped_refptr<base::SequencedTaskRunner>& read_runner,
369 const scoped_refptr<base::SequencedTaskRunner>& write_runner,
370 content::DownloadManager* download_manager)
371 : read_runner_(read_runner),
372 write_runner_(write_runner),
373 metadata_path_(GetMetadataPath(download_manager->GetBrowserContext())),
374 state_(WAITING_FOR_LOAD),
375 item_(),
376 weak_factory_(this) {
377 // Start the asynchronous task to read the persistent metadata.
378 ReadMetadata();
379 }
380
381 void DownloadMetadataManager::ManagerContext::Detach() {
382 // Delete the instance immediately if there's no work to process after a
383 // pending read completes.
384 if (get_details_callbacks_.empty() && pending_items_.empty()) {
385 delete this;
386 } else {
387 // delete the instance in OnMetadataReady.
388 state_ = DETACHED_WAIT;
389 }
390 }
391
392 void DownloadMetadataManager::ManagerContext::OnDownloadCreated(
393 content::DownloadItem* download) {
394 const uint32_t id = download->GetId();
395 if (state_ != LOAD_COMPLETE) {
396 // Observe all downloads and record operations while waiting for metadata to
397 // load.
398 download->AddObserver(this);
399 pending_items_[id].item = download;
400 } else if (id == GetDownloadId()) {
401 // Observe this one item if it is the important one.
402 DCHECK_EQ(item_, static_cast<content::DownloadItem*>(nullptr));
403 item_ = download;
404 download->AddObserver(this);
405 }
406 }
407
408 void DownloadMetadataManager::ManagerContext::SetRequest(
409 content::DownloadItem* download,
410 const ClientDownloadRequest& request) {
411 if (state_ != LOAD_COMPLETE) {
412 // Abandon the read task since |download| is the new top dog.
413 weak_factory_.InvalidateWeakPtrs();
414 state_ = LOAD_COMPLETE;
415 // Stop observing all items and drop any recorded operations.
416 ClearPendingItems();
417 }
418 // Observe this item from here on out.
419 if (item_ != download) {
420 if (item_)
421 item_->RemoveObserver(this);
422 download->AddObserver(this);
423 item_ = download;
424 }
425 // Take the request.
426 download_metadata_.reset(new DownloadMetadata);
427 download_metadata_->set_download_id(download->GetId());
428 download_metadata_->mutable_download()->mutable_download()->CopyFrom(request);
429 base::Time end_time = download->GetEndTime();
430 // The item may have yet to be marked as complete, so consider the current
431 // time as being its download time.
432 if (end_time.is_null())
433 end_time = base::Time::Now();
434 download_metadata_->mutable_download()->set_download_time_msec(
435 end_time.ToJavaTime());
436 // Persist it.
437 WriteMetadata();
438 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
439 RunCallbacks();
440 }
441
442 void DownloadMetadataManager::ManagerContext::RemoveMetadata() {
443 if (state_ != LOAD_COMPLETE) {
444 // Abandon the read task since the file is to be removed.
445 weak_factory_.InvalidateWeakPtrs();
446 state_ = LOAD_COMPLETE;
447 // Stop observing all items and drop any recorded operations.
448 ClearPendingItems();
449 }
450 // Remove any metadata.
451 download_metadata_.reset();
452 write_runner_->PostTask(
453 FROM_HERE, base::Bind(&DeleteMetadataOnWorkerPool, metadata_path_));
454 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
455 RunCallbacks();
456 }
457
458 void DownloadMetadataManager::ManagerContext::GetDownloadDetails(
459 const GetDownloadDetailsCallback& callback) {
460 if (state_ != LOAD_COMPLETE) {
461 get_details_callbacks_.push_back(callback);
462 } else {
463 if (download_metadata_) {
464 callback.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
465 download_metadata_->download())).Pass());
466 } else {
467 callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
468 }
469 }
470 }
471
472 void DownloadMetadataManager::ManagerContext::OnDownloadOpened(
473 content::DownloadItem* download) {
474 const base::Time now = base::Time::Now();
475 if (state_ != LOAD_COMPLETE)
476 pending_items_[download->GetId()].last_opened_time = now;
477 else
478 UpdateLastOpenedTime(now);
479 }
480
481 void DownloadMetadataManager::ManagerContext::OnDownloadRemoved(
482 content::DownloadItem* download) {
483 if (state_ != LOAD_COMPLETE)
484 pending_items_[download->GetId()].removed = true;
485 else
486 RemoveMetadata();
487 }
488
489 void DownloadMetadataManager::ManagerContext::OnDownloadDestroyed(
490 content::DownloadItem* download) {
491 if (state_ != LOAD_COMPLETE) {
492 // Erase the data for this item if nothing of import happened. Otherwise
493 // clear its item pointer since it is now invalid.
494 auto iter = pending_items_.find(download->GetId());
495 DCHECK(iter != pending_items_.end());
496 if (!iter->second.removed && iter->second.last_opened_time.is_null())
497 pending_items_.erase(iter);
498 else
499 iter->second.item = nullptr;
500 } else if (item_) {
501 // This item is no longer being observed.
502 DCHECK_EQ(item_, download);
503 item_ = nullptr;
504 }
505 }
506
507 DownloadMetadataManager::ManagerContext::~ManagerContext() {
508 // All downloads must have been destroyed prior to deleting the context and
509 // all recorded operations and callbacks must have been handled.
510 DCHECK(pending_items_.empty());
511 DCHECK_EQ(item_, static_cast<content::DownloadItem*>(nullptr));
512 DCHECK(get_details_callbacks_.empty());
513 }
514
515 void DownloadMetadataManager::ManagerContext::ClearPendingItems() {
516 for (const auto& value : pending_items_) {
517 if (value.second.item)
518 value.second.item->RemoveObserver(this);
519 }
520 pending_items_.clear();
521 }
522
523 void DownloadMetadataManager::ManagerContext::RunCallbacks() {
524 while (!get_details_callbacks_.empty()) {
525 const auto& callback = get_details_callbacks_.front();
526 if (download_metadata_) {
527 callback.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
528 download_metadata_->download())).Pass());
529 } else {
530 callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
531 }
532 get_details_callbacks_.pop_front();
533 }
534 }
535
536 uint32_t DownloadMetadataManager::ManagerContext::GetDownloadId() const {
537 if (state_ != LOAD_COMPLETE || !download_metadata_)
538 return content::DownloadItem::kInvalidId;
539 return download_metadata_->download_id();
540 }
541
542 void DownloadMetadataManager::ManagerContext::ReadMetadata() {
543 DCHECK_NE(state_, LOAD_COMPLETE);
544
545 DownloadMetadata* metadata = new DownloadMetadata();
546 // Do not block shutdown on this read since nothing will come of it.
547 read_runner_->PostTaskAndReply(
548 FROM_HERE,
549 base::Bind(&ReadMetadataOnWorkerPool, metadata_path_, metadata),
550 base::Bind(&DownloadMetadataManager::ManagerContext::OnMetadataReady,
551 weak_factory_.GetWeakPtr(),
552 base::Passed(make_scoped_ptr(metadata))));
553 }
554
555 void DownloadMetadataManager::ManagerContext::OnMetadataReady(
556 scoped_ptr<DownloadMetadata> download_metadata) {
557 DCHECK_NE(state_, LOAD_COMPLETE);
558
559 const bool is_detached = (state_ == DETACHED_WAIT);
560
561 // Note that any available data has been read.
562 state_ = LOAD_COMPLETE;
563 if (download_metadata->has_download_id())
564 download_metadata_ = download_metadata.Pass();
565 else
566 download_metadata_.reset();
567
568 // Process all operations that had been held while waiting for the metadata.
569 content::DownloadItem* download = nullptr;
570 {
571 const auto& iter = pending_items_.find(GetDownloadId());
572 if (iter != pending_items_.end()) {
573 const ItemData& item_data = iter->second;
574 download = item_data.item; // non-null if not destroyed.
575 if (item_data.removed)
576 RemoveMetadata();
577 else if (!item_data.last_opened_time.is_null())
578 UpdateLastOpenedTime(item_data.last_opened_time);
579 }
580 }
581
582 // Stop observing all items.
583 ClearPendingItems();
584
585 // If the download was known, observe it from here on out.
586 if (download) {
587 download->AddObserver(this);
588 item_ = download;
589 }
590
591 // Run callbacks.
592 RunCallbacks();
593
594 // Delete the context now if it has been detached.
595 if (is_detached)
596 delete this;
597 }
598
599 void DownloadMetadataManager::ManagerContext::UpdateLastOpenedTime(
600 const base::Time& last_opened_time) {
601 download_metadata_->mutable_download()->set_open_time_msec(
602 last_opened_time.ToJavaTime());
603 WriteMetadata();
604 }
605
606 void DownloadMetadataManager::ManagerContext::WriteMetadata() {
607 write_runner_->PostTask(
608 FROM_HERE,
609 base::Bind(&WriteMetadataOnWorkerPool,
610 metadata_path_,
611 base::Owned(new DownloadMetadata(*download_metadata_))));
612 }
613
614 } // namespace safe_browsing
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698