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