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 "content/public/test/download_test_observer.h"
6
7 #include <vector>
8
9 #include "base/bind.h"
10 #include "base/check_op.h"
11 #include "base/notreached.h"
12 #include "base/run_loop.h"
13 #include "base/stl_util.h"
14 #include "components/download/public/common/download_url_parameters.h"
15 #include "content/public/browser/browser_task_traits.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/download_manager.h"
18 #include "content/public/test/test_utils.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 namespace content {
22
DownloadUpdatedObserver(download::DownloadItem * item,DownloadUpdatedObserver::EventFilter filter)23 DownloadUpdatedObserver::DownloadUpdatedObserver(
24 download::DownloadItem* item,
25 DownloadUpdatedObserver::EventFilter filter)
26 : item_(item), filter_(filter), waiting_(false), event_seen_(false) {
27 item->AddObserver(this);
28 }
29
~DownloadUpdatedObserver()30 DownloadUpdatedObserver::~DownloadUpdatedObserver() {
31 if (item_)
32 item_->RemoveObserver(this);
33 }
34
WaitForEvent()35 bool DownloadUpdatedObserver::WaitForEvent() {
36 if (item_ && filter_.Run(item_))
37 event_seen_ = true;
38 if (event_seen_)
39 return true;
40
41 waiting_ = true;
42 RunMessageLoop();
43 waiting_ = false;
44 return event_seen_;
45 }
46
OnDownloadUpdated(download::DownloadItem * item)47 void DownloadUpdatedObserver::OnDownloadUpdated(download::DownloadItem* item) {
48 DCHECK_EQ(item_, item);
49 if (filter_.Run(item_))
50 event_seen_ = true;
51 if (waiting_ && event_seen_)
52 base::RunLoop::QuitCurrentWhenIdleDeprecated();
53 }
54
OnDownloadDestroyed(download::DownloadItem * item)55 void DownloadUpdatedObserver::OnDownloadDestroyed(
56 download::DownloadItem* item) {
57 DCHECK_EQ(item_, item);
58 item_->RemoveObserver(this);
59 item_ = nullptr;
60 if (waiting_)
61 base::RunLoop::QuitCurrentWhenIdleDeprecated();
62 }
63
DownloadTestObserver(DownloadManager * download_manager,size_t wait_count,DangerousDownloadAction dangerous_download_action)64 DownloadTestObserver::DownloadTestObserver(
65 DownloadManager* download_manager,
66 size_t wait_count,
67 DangerousDownloadAction dangerous_download_action)
68 : download_manager_(download_manager),
69 wait_count_(wait_count),
70 finished_downloads_at_construction_(0),
71 waiting_(false),
72 dangerous_download_action_(dangerous_download_action) {}
73
~DownloadTestObserver()74 DownloadTestObserver::~DownloadTestObserver() {
75 for (DownloadSet::iterator it = downloads_observed_.begin();
76 it != downloads_observed_.end(); ++it)
77 (*it)->RemoveObserver(this);
78
79 if (download_manager_)
80 download_manager_->RemoveObserver(this);
81 }
82
Init()83 void DownloadTestObserver::Init() {
84 download_manager_->AddObserver(this);
85 std::vector<download::DownloadItem*> downloads;
86 download_manager_->GetAllDownloads(&downloads);
87 for (std::vector<download::DownloadItem*>::iterator it = downloads.begin();
88 it != downloads.end(); ++it) {
89 OnDownloadCreated(download_manager_, *it);
90 }
91 finished_downloads_at_construction_ = finished_downloads_.size();
92 states_observed_.clear();
93 }
94
ManagerGoingDown(DownloadManager * manager)95 void DownloadTestObserver::ManagerGoingDown(DownloadManager* manager) {
96 CHECK_EQ(manager, download_manager_);
97 download_manager_ = nullptr;
98 SignalIfFinished();
99 }
100
WaitForFinished()101 void DownloadTestObserver::WaitForFinished() {
102 if (!IsFinished()) {
103 waiting_ = true;
104 RunMessageLoop();
105 waiting_ = false;
106 }
107 }
108
IsFinished() const109 bool DownloadTestObserver::IsFinished() const {
110 return (finished_downloads_.size() - finished_downloads_at_construction_ >=
111 wait_count_) ||
112 (download_manager_ == nullptr);
113 }
114
OnDownloadCreated(DownloadManager * manager,download::DownloadItem * item)115 void DownloadTestObserver::OnDownloadCreated(DownloadManager* manager,
116 download::DownloadItem* item) {
117 // NOTE: This method is called both by DownloadManager when a download is
118 // created as well as in DownloadTestObserver::Init() for downloads that
119 // existed before |this| was created.
120 OnDownloadUpdated(item);
121 DownloadSet::const_iterator finished_it(finished_downloads_.find(item));
122 // If it isn't finished, start observing it.
123 if (finished_it == finished_downloads_.end()) {
124 item->AddObserver(this);
125 downloads_observed_.insert(item);
126 }
127 }
128
OnDownloadDestroyed(download::DownloadItem * download)129 void DownloadTestObserver::OnDownloadDestroyed(
130 download::DownloadItem* download) {
131 // Stop observing. Do not do anything with it, as it is about to be gone.
132 DownloadSet::iterator it = downloads_observed_.find(download);
133 ASSERT_TRUE(it != downloads_observed_.end());
134 downloads_observed_.erase(it);
135 download->RemoveObserver(this);
136 }
137
OnDownloadUpdated(download::DownloadItem * download)138 void DownloadTestObserver::OnDownloadUpdated(download::DownloadItem* download) {
139 // Real UI code gets the user's response after returning from the observer.
140 if (download->IsDangerous() &&
141 !base::Contains(dangerous_downloads_seen_, download->GetId())) {
142 dangerous_downloads_seen_.insert(download->GetId());
143
144 // Calling ValidateDangerousDownload() at this point will
145 // cause the download to be completed twice. Do what the real UI
146 // code does: make the call as a delayed task.
147 switch (dangerous_download_action_) {
148 case ON_DANGEROUS_DOWNLOAD_ACCEPT:
149 // Fake user click on "Accept". Delay the actual click, as the
150 // real UI would.
151 GetUIThreadTaskRunner({})->PostTask(
152 FROM_HERE,
153 base::BindOnce(&DownloadTestObserver::AcceptDangerousDownload,
154 weak_factory_.GetWeakPtr(), download->GetId()));
155 break;
156
157 case ON_DANGEROUS_DOWNLOAD_DENY:
158 // Fake a user click on "Deny". Delay the actual click, as the
159 // real UI would.
160 GetUIThreadTaskRunner({})->PostTask(
161 FROM_HERE,
162 base::BindOnce(&DownloadTestObserver::DenyDangerousDownload,
163 weak_factory_.GetWeakPtr(), download->GetId()));
164 break;
165
166 case ON_DANGEROUS_DOWNLOAD_FAIL:
167 ADD_FAILURE() << "Unexpected dangerous download item.";
168 break;
169
170 case ON_DANGEROUS_DOWNLOAD_IGNORE:
171 break;
172
173 case ON_DANGEROUS_DOWNLOAD_QUIT:
174 DownloadInFinalState(download);
175 break;
176
177 default:
178 NOTREACHED();
179 }
180 }
181
182 if (IsDownloadInFinalState(download))
183 DownloadInFinalState(download);
184 }
185
NumDangerousDownloadsSeen() const186 size_t DownloadTestObserver::NumDangerousDownloadsSeen() const {
187 return dangerous_downloads_seen_.size();
188 }
189
NumDownloadsSeenInState(download::DownloadItem::DownloadState state) const190 size_t DownloadTestObserver::NumDownloadsSeenInState(
191 download::DownloadItem::DownloadState state) const {
192 StateMap::const_iterator it = states_observed_.find(state);
193
194 if (it == states_observed_.end())
195 return 0;
196
197 return it->second;
198 }
199
DownloadInFinalState(download::DownloadItem * download)200 void DownloadTestObserver::DownloadInFinalState(
201 download::DownloadItem* download) {
202 if (finished_downloads_.find(download) != finished_downloads_.end()) {
203 // We've already seen the final state on this download.
204 return;
205 }
206
207 // Record the transition.
208 finished_downloads_.insert(download);
209
210 // Record the state.
211 states_observed_[download->GetState()]++; // Initializes to 0 the first time.
212
213 SignalIfFinished();
214 }
215
SignalIfFinished()216 void DownloadTestObserver::SignalIfFinished() {
217 if (waiting_ && IsFinished())
218 base::RunLoop::QuitCurrentWhenIdleDeprecated();
219 }
220
AcceptDangerousDownload(uint32_t download_id)221 void DownloadTestObserver::AcceptDangerousDownload(uint32_t download_id) {
222 // Download manager was shutdown before the UI thread could accept the
223 // download.
224 if (!download_manager_)
225 return;
226 download::DownloadItem* download =
227 download_manager_->GetDownload(download_id);
228 if (download && !download->IsDone())
229 download->ValidateDangerousDownload();
230 }
231
DenyDangerousDownload(uint32_t download_id)232 void DownloadTestObserver::DenyDangerousDownload(uint32_t download_id) {
233 // Download manager was shutdown before the UI thread could deny the
234 // download.
235 if (!download_manager_)
236 return;
237 download::DownloadItem* download =
238 download_manager_->GetDownload(download_id);
239 if (download && !download->IsDone())
240 download->Remove();
241 }
242
DownloadTestObserverTerminal(DownloadManager * download_manager,size_t wait_count,DangerousDownloadAction dangerous_download_action)243 DownloadTestObserverTerminal::DownloadTestObserverTerminal(
244 DownloadManager* download_manager,
245 size_t wait_count,
246 DangerousDownloadAction dangerous_download_action)
247 : DownloadTestObserver(download_manager,
248 wait_count,
249 dangerous_download_action) {
250 // You can't rely on overriden virtual functions in a base class constructor;
251 // the virtual function table hasn't been set up yet. So, we have to do any
252 // work that depends on those functions in the derived class constructor
253 // instead. In this case, it's because of |IsDownloadInFinalState()|.
254 Init();
255 }
256
~DownloadTestObserverTerminal()257 DownloadTestObserverTerminal::~DownloadTestObserverTerminal() {
258 }
259
IsDownloadInFinalState(download::DownloadItem * download)260 bool DownloadTestObserverTerminal::IsDownloadInFinalState(
261 download::DownloadItem* download) {
262 return download->IsDone();
263 }
264
DownloadTestObserverInProgress(DownloadManager * download_manager,size_t wait_count)265 DownloadTestObserverInProgress::DownloadTestObserverInProgress(
266 DownloadManager* download_manager,
267 size_t wait_count)
268 : DownloadTestObserver(download_manager,
269 wait_count,
270 ON_DANGEROUS_DOWNLOAD_ACCEPT) {
271 // You can't override virtual functions in a base class constructor; the
272 // virtual function table hasn't been set up yet. So, we have to do any
273 // work that depends on those functions in the derived class constructor
274 // instead. In this case, it's because of |IsDownloadInFinalState()|.
275 Init();
276 }
277
~DownloadTestObserverInProgress()278 DownloadTestObserverInProgress::~DownloadTestObserverInProgress() {
279 }
280
IsDownloadInFinalState(download::DownloadItem * download)281 bool DownloadTestObserverInProgress::IsDownloadInFinalState(
282 download::DownloadItem* download) {
283 return (download->GetState() == download::DownloadItem::IN_PROGRESS) &&
284 !download->GetTargetFilePath().empty();
285 }
286
DownloadTestObserverInterrupted(DownloadManager * download_manager,size_t wait_count,DangerousDownloadAction dangerous_download_action)287 DownloadTestObserverInterrupted::DownloadTestObserverInterrupted(
288 DownloadManager* download_manager,
289 size_t wait_count,
290 DangerousDownloadAction dangerous_download_action)
291 : DownloadTestObserver(download_manager,
292 wait_count,
293 dangerous_download_action) {
294 // You can't rely on overriden virtual functions in a base class constructor;
295 // the virtual function table hasn't been set up yet. So, we have to do any
296 // work that depends on those functions in the derived class constructor
297 // instead. In this case, it's because of |IsDownloadInFinalState()|.
298 Init();
299 }
300
~DownloadTestObserverInterrupted()301 DownloadTestObserverInterrupted::~DownloadTestObserverInterrupted() {
302 }
303
IsDownloadInFinalState(download::DownloadItem * download)304 bool DownloadTestObserverInterrupted::IsDownloadInFinalState(
305 download::DownloadItem* download) {
306 return download->GetState() == download::DownloadItem::INTERRUPTED;
307 }
308
309 void PingIOThread(int cycle, base::OnceClosure callback);
310
311 // Helper method to post a task to IO thread to ensure remaining operations on
312 // the IO thread complete.
PingFileThread(int cycle,base::OnceClosure callback)313 void PingFileThread(int cycle, base::OnceClosure callback) {
314 GetIOThreadTaskRunner({})->PostTask(
315 FROM_HERE, base::BindOnce(&PingIOThread, cycle, std::move(callback)));
316 }
317
318 // Post a task to file thread, and wait for it to be posted back on to the IO
319 // thread if |cycle| is larger than 1. This ensures that all remaining
320 // operations on the IO thread complete.
PingIOThread(int cycle,base::OnceClosure callback)321 void PingIOThread(int cycle, base::OnceClosure callback) {
322 if (--cycle) {
323 DownloadManager::GetTaskRunner()->PostTask(
324 FROM_HERE, base::BindOnce(&PingFileThread, cycle, std::move(callback)));
325 } else {
326 GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
327 }
328 }
329
DownloadTestFlushObserver(DownloadManager * download_manager)330 DownloadTestFlushObserver::DownloadTestFlushObserver(
331 DownloadManager* download_manager)
332 : download_manager_(download_manager),
333 waiting_for_zero_inprogress_(true) {}
334
WaitForFlush()335 void DownloadTestFlushObserver::WaitForFlush() {
336 DCHECK_CURRENTLY_ON(BrowserThread::UI);
337 download_manager_->AddObserver(this);
338 // The wait condition may have been met before WaitForFlush() was called.
339 CheckDownloadsInProgress(true);
340 run_loop_.Run();
341 }
342
OnDownloadCreated(DownloadManager * manager,download::DownloadItem * item)343 void DownloadTestFlushObserver::OnDownloadCreated(
344 DownloadManager* manager,
345 download::DownloadItem* item) {
346 CheckDownloadsInProgress(true);
347 }
348
ManagerGoingDown(DownloadManager * manager)349 void DownloadTestFlushObserver::ManagerGoingDown(DownloadManager* manager) {
350 download_manager_ = nullptr;
351 }
352
OnDownloadDestroyed(download::DownloadItem * download)353 void DownloadTestFlushObserver::OnDownloadDestroyed(
354 download::DownloadItem* download) {
355 // Stop observing. Do not do anything with it, as it is about to be gone.
356 DownloadSet::iterator it = downloads_observed_.find(download);
357 ASSERT_TRUE(it != downloads_observed_.end());
358 downloads_observed_.erase(it);
359 download->RemoveObserver(this);
360 }
361
OnDownloadUpdated(download::DownloadItem * download)362 void DownloadTestFlushObserver::OnDownloadUpdated(
363 download::DownloadItem* download) {
364 // No change in download::DownloadItem set on manager.
365 CheckDownloadsInProgress(false);
366 }
367
~DownloadTestFlushObserver()368 DownloadTestFlushObserver::~DownloadTestFlushObserver() {
369 if (!download_manager_)
370 return;
371
372 download_manager_->RemoveObserver(this);
373 for (DownloadSet::iterator it = downloads_observed_.begin();
374 it != downloads_observed_.end(); ++it) {
375 (*it)->RemoveObserver(this);
376 }
377 }
378
379 // If we're waiting for that flush point, check the number
380 // of downloads in the IN_PROGRESS state and take appropriate
381 // action. If requested, also observes all downloads while iterating.
CheckDownloadsInProgress(bool observe_downloads)382 void DownloadTestFlushObserver::CheckDownloadsInProgress(
383 bool observe_downloads) {
384 if (waiting_for_zero_inprogress_) {
385 int count = 0;
386
387 std::vector<download::DownloadItem*> downloads;
388 download_manager_->GetAllDownloads(&downloads);
389 for (std::vector<download::DownloadItem*>::iterator it = downloads.begin();
390 it != downloads.end(); ++it) {
391 if ((*it)->GetState() == download::DownloadItem::IN_PROGRESS)
392 count++;
393 if (observe_downloads) {
394 if (downloads_observed_.find(*it) == downloads_observed_.end()) {
395 (*it)->AddObserver(this);
396 downloads_observed_.insert(*it);
397 }
398 // Download items are forever, and we don't want to make
399 // assumptions about future state transitions, so once we
400 // start observing them, we don't stop until destruction.
401 }
402 }
403
404 if (count == 0) {
405 waiting_for_zero_inprogress_ = false;
406 // Stop observing download::DownloadItems. We maintain the observation
407 // of DownloadManager so that we don't have to independently track
408 // whether we are observing it for conditional destruction.
409 for (DownloadSet::iterator it = downloads_observed_.begin();
410 it != downloads_observed_.end(); ++it) {
411 (*it)->RemoveObserver(this);
412 }
413 downloads_observed_.clear();
414
415 // Trigger next step. We need to go past the IO thread twice, as
416 // there's a self-task posting in the IO thread cancel path.
417 DownloadManager::GetTaskRunner()->PostTask(
418 FROM_HERE,
419 base::BindOnce(&PingFileThread, 2, run_loop_.QuitClosure()));
420 }
421 }
422 }
423
DownloadTestItemCreationObserver()424 DownloadTestItemCreationObserver::DownloadTestItemCreationObserver()
425 : download_id_(download::DownloadItem::kInvalidId),
426 interrupt_reason_(download::DOWNLOAD_INTERRUPT_REASON_NONE),
427 called_back_count_(0),
428 waiting_(false) {}
429
~DownloadTestItemCreationObserver()430 DownloadTestItemCreationObserver::~DownloadTestItemCreationObserver() {
431 }
432
WaitForDownloadItemCreation()433 void DownloadTestItemCreationObserver::WaitForDownloadItemCreation() {
434 DCHECK_CURRENTLY_ON(BrowserThread::UI);
435
436 if (called_back_count_ == 0) {
437 waiting_ = true;
438 RunMessageLoop();
439 waiting_ = false;
440 }
441 }
442
DownloadItemCreationCallback(download::DownloadItem * item,download::DownloadInterruptReason interrupt_reason)443 void DownloadTestItemCreationObserver::DownloadItemCreationCallback(
444 download::DownloadItem* item,
445 download::DownloadInterruptReason interrupt_reason) {
446 DCHECK_CURRENTLY_ON(BrowserThread::UI);
447
448 if (item)
449 download_id_ = item->GetId();
450 interrupt_reason_ = interrupt_reason;
451 ++called_back_count_;
452 DCHECK_EQ(1u, called_back_count_);
453
454 if (waiting_)
455 base::RunLoop::QuitCurrentWhenIdleDeprecated();
456 }
457
458 download::DownloadUrlParameters::OnStartedCallback
callback()459 DownloadTestItemCreationObserver::callback() {
460 return base::BindOnce(
461 &DownloadTestItemCreationObserver::DownloadItemCreationCallback, this);
462 }
463
SavePackageFinishedObserver(DownloadManager * manager,base::OnceClosure callback)464 SavePackageFinishedObserver::SavePackageFinishedObserver(
465 DownloadManager* manager,
466 base::OnceClosure callback)
467 : download_manager_(manager),
468 download_(nullptr),
469 callback_(std::move(callback)) {
470 download_manager_->AddObserver(this);
471 }
472
~SavePackageFinishedObserver()473 SavePackageFinishedObserver::~SavePackageFinishedObserver() {
474 if (download_manager_)
475 download_manager_->RemoveObserver(this);
476
477 if (download_)
478 download_->RemoveObserver(this);
479 }
480
OnDownloadUpdated(download::DownloadItem * download)481 void SavePackageFinishedObserver::OnDownloadUpdated(
482 download::DownloadItem* download) {
483 if (download->GetState() == download::DownloadItem::COMPLETE ||
484 download->GetState() == download::DownloadItem::CANCELLED) {
485 std::move(callback_).Run();
486 }
487 }
488
OnDownloadDestroyed(download::DownloadItem * download)489 void SavePackageFinishedObserver::OnDownloadDestroyed(
490 download::DownloadItem* download) {
491 download_->RemoveObserver(this);
492 download_ = nullptr;
493 }
494
OnDownloadCreated(DownloadManager * manager,download::DownloadItem * download)495 void SavePackageFinishedObserver::OnDownloadCreated(
496 DownloadManager* manager,
497 download::DownloadItem* download) {
498 download_ = download;
499 download->AddObserver(this);
500 }
501
ManagerGoingDown(DownloadManager * manager)502 void SavePackageFinishedObserver::ManagerGoingDown(DownloadManager* manager) {
503 download_->RemoveObserver(this);
504 download_ = nullptr;
505 download_manager_->RemoveObserver(this);
506 download_manager_ = nullptr;
507 }
508
509 } // namespace content
510