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