1 // Copyright 2020 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 "weblayer/test/weblayer_browser_test.h"
6 
7 #include "base/files/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/path_service.h"
10 #include "base/run_loop.h"
11 #include "base/task/post_task.h"
12 #include "base/test/bind.h"
13 #include "base/threading/thread_restrictions.h"
14 #include "content/public/browser/download_manager.h"
15 #include "content/public/browser/web_contents.h"
16 #include "content/public/test/slow_download_http_response.h"
17 #include "net/test/embedded_test_server/embedded_test_server.h"
18 #include "weblayer/browser/browser_context_impl.h"
19 #include "weblayer/browser/download_manager_delegate_impl.h"
20 #include "weblayer/browser/profile_impl.h"
21 #include "weblayer/browser/tab_impl.h"
22 #include "weblayer/public/download.h"
23 #include "weblayer/public/download_delegate.h"
24 #include "weblayer/public/navigation_controller.h"
25 #include "weblayer/shell/browser/shell.h"
26 #include "weblayer/test/test_navigation_observer.h"
27 #include "weblayer/test/weblayer_browser_test_utils.h"
28 
29 namespace weblayer {
30 
31 namespace {
32 
33 class DownloadBrowserTest : public WebLayerBrowserTest,
34                             public DownloadDelegate {
35  public:
36   DownloadBrowserTest() = default;
37   ~DownloadBrowserTest() override = default;
38 
SetUpOnMainThread()39   void SetUpOnMainThread() override {
40     embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
41         &content::SlowDownloadHttpResponse::HandleSlowDownloadRequest));
42     ASSERT_TRUE(embedded_test_server()->Start());
43 
44     allow_run_loop_ = std::make_unique<base::RunLoop>();
45     started_run_loop_ = std::make_unique<base::RunLoop>();
46     intercept_run_loop_ = std::make_unique<base::RunLoop>();
47     completed_run_loop_ = std::make_unique<base::RunLoop>();
48     failed_run_loop_ = std::make_unique<base::RunLoop>();
49 
50     Tab* tab = shell()->tab();
51     TabImpl* tab_impl = static_cast<TabImpl*>(tab);
52 
53     tab_impl->profile()->SetDownloadDelegate(this);
54 
55     auto* browser_context = tab_impl->web_contents()->GetBrowserContext();
56     auto* download_manager_delegate =
57         content::BrowserContext::GetDownloadManager(browser_context)
58             ->GetDelegate();
59     static_cast<DownloadManagerDelegateImpl*>(download_manager_delegate)
60         ->set_download_dropped_closure_for_testing(base::BindRepeating(
61             &DownloadBrowserTest::DownloadDropped, base::Unretained(this)));
62   }
63 
WaitForAllow()64   void WaitForAllow() { allow_run_loop_->Run(); }
WaitForIntercept()65   void WaitForIntercept() { intercept_run_loop_->Run(); }
WaitForStarted()66   void WaitForStarted() { started_run_loop_->Run(); }
WaitForCompleted()67   void WaitForCompleted() { completed_run_loop_->Run(); }
WaitForFailed()68   void WaitForFailed() { failed_run_loop_->Run(); }
69 
set_intercept()70   void set_intercept() { intercept_ = true; }
set_disallow()71   void set_disallow() { allow_ = false; }
set_started_callback(base::OnceCallback<void (Download * download)> callback)72   void set_started_callback(
73       base::OnceCallback<void(Download* download)> callback) {
74     started_callback_ = std::move(callback);
75   }
set_failed_callback(base::OnceCallback<void (Download * download)> callback)76   void set_failed_callback(
77       base::OnceCallback<void(Download* download)> callback) {
78     failed_callback_ = std::move(callback);
79   }
started()80   bool started() { return started_; }
download_location()81   base::FilePath download_location() { return download_location_; }
total_bytes()82   int64_t total_bytes() { return total_bytes_; }
download_state()83   DownloadError download_state() { return download_state_; }
mime_type()84   std::string mime_type() { return mime_type_; }
completed_count()85   int completed_count() { return completed_count_; }
failed_count()86   int failed_count() { return failed_count_; }
download_dropped_count()87   int download_dropped_count() { return download_dropped_count_; }
88 
89  private:
90   // DownloadDelegate implementation:
AllowDownload(Tab * tab,const GURL & url,const std::string & request_method,base::Optional<url::Origin> request_initiator,AllowDownloadCallback callback)91   void AllowDownload(Tab* tab,
92                      const GURL& url,
93                      const std::string& request_method,
94                      base::Optional<url::Origin> request_initiator,
95                      AllowDownloadCallback callback) override {
96     std::move(callback).Run(allow_);
97     allow_run_loop_->Quit();
98   }
99 
InterceptDownload(const GURL & url,const std::string & user_agent,const std::string & content_disposition,const std::string & mime_type,int64_t content_length)100   bool InterceptDownload(const GURL& url,
101                          const std::string& user_agent,
102                          const std::string& content_disposition,
103                          const std::string& mime_type,
104                          int64_t content_length) override {
105     intercept_run_loop_->Quit();
106     return intercept_;
107   }
108 
DownloadStarted(Download * download)109   void DownloadStarted(Download* download) override {
110     started_ = true;
111     started_run_loop_->Quit();
112 
113     CHECK_EQ(download->GetState(), DownloadState::kInProgress);
114 
115     if (started_callback_)
116       std::move(started_callback_).Run(download);
117   }
118 
DownloadCompleted(Download * download)119   void DownloadCompleted(Download* download) override {
120     completed_count_++;
121     download_location_ = download->GetLocation();
122     total_bytes_ = download->GetTotalBytes();
123     download_state_ = download->GetError();
124     mime_type_ = download->GetMimeType();
125     CHECK_EQ(download->GetReceivedBytes(), total_bytes_);
126     CHECK_EQ(download->GetState(), DownloadState::kComplete);
127     completed_run_loop_->Quit();
128   }
129 
DownloadFailed(Download * download)130   void DownloadFailed(Download* download) override {
131     failed_count_++;
132     download_state_ = download->GetError();
133     failed_run_loop_->Quit();
134 
135     if (failed_callback_)
136       std::move(failed_callback_).Run(download);
137   }
138 
DownloadDropped()139   void DownloadDropped() { download_dropped_count_++; }
140 
141   bool intercept_ = false;
142   bool allow_ = true;
143   bool started_ = false;
144   base::OnceCallback<void(Download* download)> started_callback_;
145   base::OnceCallback<void(Download* download)> failed_callback_;
146   base::FilePath download_location_;
147   int64_t total_bytes_ = 0;
148   DownloadError download_state_ = DownloadError::kNoError;
149   std::string mime_type_;
150   int completed_count_ = 0;
151   int failed_count_ = 0;
152   int download_dropped_count_ = 0;
153   std::unique_ptr<base::RunLoop> allow_run_loop_;
154   std::unique_ptr<base::RunLoop> intercept_run_loop_;
155   std::unique_ptr<base::RunLoop> started_run_loop_;
156   std::unique_ptr<base::RunLoop> completed_run_loop_;
157   std::unique_ptr<base::RunLoop> failed_run_loop_;
158 };
159 
160 }  // namespace
161 
162 // Ensures that if the delegate disallows the downloads then WebLayer
163 // doesn't download it.
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,DisallowNoDownload)164 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, DisallowNoDownload) {
165   set_disallow();
166 
167   GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
168 
169   // Downloads always count as failed navigations.
170   TestNavigationObserver observer(
171       url, TestNavigationObserver::NavigationEvent::kFailure, shell());
172   shell()->tab()->GetNavigationController()->Navigate(url);
173   observer.Wait();
174 
175   WaitForAllow();
176 
177   EXPECT_FALSE(started());
178   EXPECT_EQ(completed_count(), 0);
179   EXPECT_EQ(failed_count(), 0);
180   EXPECT_EQ(download_dropped_count(), 1);
181 }
182 
183 // Ensures that if the delegate chooses to intercept downloads then WebLayer
184 // doesn't download it.
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,InterceptNoDownload)185 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, InterceptNoDownload) {
186   set_intercept();
187 
188   GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
189 
190   shell()->tab()->GetNavigationController()->Navigate(url);
191 
192   WaitForIntercept();
193 
194   EXPECT_FALSE(started());
195   EXPECT_EQ(completed_count(), 0);
196   EXPECT_EQ(failed_count(), 0);
197   EXPECT_EQ(download_dropped_count(), 1);
198 }
199 
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,Basic)200 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Basic) {
201   GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
202 
203   shell()->tab()->GetNavigationController()->Navigate(url);
204 
205   WaitForCompleted();
206 
207   EXPECT_TRUE(started());
208   EXPECT_EQ(completed_count(), 1);
209   EXPECT_EQ(failed_count(), 0);
210   EXPECT_EQ(download_dropped_count(), 0);
211   EXPECT_EQ(download_state(), DownloadError::kNoError);
212   EXPECT_EQ(mime_type(), "text/html");
213 
214   // Check that the size on disk matches what's expected.
215   {
216     base::ScopedAllowBlockingForTesting allow_blocking;
217     int64_t downloaded_file_size, original_file_size;
218     EXPECT_TRUE(base::GetFileSize(download_location(), &downloaded_file_size));
219     base::FilePath test_data_dir;
220     CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
221     EXPECT_TRUE(base::GetFileSize(
222         test_data_dir.Append(base::FilePath(
223             FILE_PATH_LITERAL("weblayer/test/data/content-disposition.html"))),
224         &original_file_size));
225     EXPECT_EQ(downloaded_file_size, total_bytes());
226   }
227 
228   // Ensure browser tests don't write to the default machine download directory
229   // to avoid filing it up.
230   EXPECT_NE(BrowserContextImpl::GetDefaultDownloadDirectory(),
231             download_location().DirName());
232 }
233 
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,OverrideDownloadDirectory)234 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, OverrideDownloadDirectory) {
235   base::ScopedAllowBlockingForTesting allow_blocking;
236   base::ScopedTempDir download_dir;
237   ASSERT_TRUE(download_dir.CreateUniqueTempDir());
238 
239   TabImpl* tab_impl = static_cast<TabImpl*>(shell()->tab());
240   auto* browser_context = tab_impl->web_contents()->GetBrowserContext();
241   auto* browser_context_impl =
242       static_cast<BrowserContextImpl*>(browser_context);
243   browser_context_impl->profile_impl()->SetDownloadDirectory(
244       download_dir.GetPath());
245 
246   GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
247 
248   shell()->tab()->GetNavigationController()->Navigate(url);
249 
250   WaitForCompleted();
251 
252   EXPECT_EQ(completed_count(), 1);
253   EXPECT_EQ(failed_count(), 0);
254   EXPECT_EQ(download_dir.GetPath(), download_location().DirName());
255 }
256 
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,Cancel)257 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Cancel) {
258   set_started_callback(base::BindLambdaForTesting([&](Download* download) {
259     download->Cancel();
260 
261     // Also allow the download to complete.
262     GURL url = embedded_test_server()->GetURL(
263         content::SlowDownloadHttpResponse::kFinishSlowResponseUrl);
264     shell()->tab()->GetNavigationController()->Navigate(url);
265   }));
266 
267   set_failed_callback(base::BindLambdaForTesting([](Download* download) {
268     CHECK_EQ(download->GetState(), DownloadState::kCancelled);
269   }));
270 
271   // Create a request that doesn't complete right away to avoid flakiness.
272   GURL url(embedded_test_server()->GetURL(
273       content::SlowDownloadHttpResponse::kKnownSizeUrl));
274 
275   shell()->tab()->GetNavigationController()->Navigate(url);
276 
277   WaitForFailed();
278   EXPECT_EQ(completed_count(), 0);
279   EXPECT_EQ(failed_count(), 1);
280   EXPECT_EQ(download_dropped_count(), 0);
281   EXPECT_EQ(download_state(), DownloadError::kCancelled);
282 }
283 
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,PauseResume)284 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, PauseResume) {
285   // Add an initial navigation to avoid the tab being deleted if the first
286   // navigation is a download, since we use the tab for convenience in the
287   // lambda.
288   OneShotNavigationObserver observer(shell());
289   shell()->tab()->GetNavigationController()->Navigate(GURL("about:blank"));
290   observer.WaitForNavigation();
291 
292   set_started_callback(base::BindLambdaForTesting([&](Download* download) {
293     download->Pause();
294     GURL url = embedded_test_server()->GetURL(
295         content::SlowDownloadHttpResponse::kFinishSlowResponseUrl);
296     base::SequencedTaskRunnerHandle::Get()->PostTask(
297         FROM_HERE, base::BindOnce(
298                        [](Download* download, Shell* shell, const GURL& url) {
299                          CHECK_EQ(download->GetState(), DownloadState::kPaused);
300                          download->Resume();
301 
302                          // Also allow the download to complete.
303                          shell->tab()->GetNavigationController()->Navigate(url);
304                        },
305                        download, shell(), url));
306   }));
307 
308   // Create a request that doesn't complete right away to avoid flakiness.
309   GURL url(embedded_test_server()->GetURL(
310       content::SlowDownloadHttpResponse::kKnownSizeUrl));
311   shell()->tab()->GetNavigationController()->Navigate(url);
312 
313   WaitForCompleted();
314   EXPECT_EQ(completed_count(), 1);
315   EXPECT_EQ(failed_count(), 0);
316   EXPECT_EQ(download_dropped_count(), 0);
317 }
318 
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,NetworkError)319 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, NetworkError) {
320   set_failed_callback(base::BindLambdaForTesting([](Download* download) {
321     CHECK_EQ(download->GetState(), DownloadState::kFailed);
322   }));
323 
324   // Create a request that doesn't complete right away.
325   GURL url(embedded_test_server()->GetURL(
326       content::SlowDownloadHttpResponse::kKnownSizeUrl));
327 
328   shell()->tab()->GetNavigationController()->Navigate(url);
329 
330   WaitForStarted();
331   EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
332 
333   WaitForFailed();
334   EXPECT_EQ(completed_count(), 0);
335   EXPECT_EQ(failed_count(), 1);
336   EXPECT_EQ(download_dropped_count(), 0);
337   EXPECT_EQ(download_state(), DownloadError::kConnectivityError);
338 }
339 
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest,PendingOnExist)340 IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, PendingOnExist) {
341   // Create a request that doesn't complete right away.
342   GURL url(embedded_test_server()->GetURL(
343       content::SlowDownloadHttpResponse::kKnownSizeUrl));
344 
345   shell()->tab()->GetNavigationController()->Navigate(url);
346 
347   WaitForStarted();
348 
349   // If this test crashes later then there'd be a regression.
350 }
351 
352 }  // namespace weblayer
353