// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "weblayer/test/weblayer_browser_test.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/test/bind.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_contents.h" #include "content/public/test/slow_download_http_response.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/download_manager_delegate_impl.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/public/download.h" #include "weblayer/public/download_delegate.h" #include "weblayer/public/navigation_controller.h" #include "weblayer/shell/browser/shell.h" #include "weblayer/test/test_navigation_observer.h" #include "weblayer/test/weblayer_browser_test_utils.h" namespace weblayer { namespace { class DownloadBrowserTest : public WebLayerBrowserTest, public DownloadDelegate { public: DownloadBrowserTest() = default; ~DownloadBrowserTest() override = default; void SetUpOnMainThread() override { embedded_test_server()->RegisterRequestHandler(base::BindRepeating( &content::SlowDownloadHttpResponse::HandleSlowDownloadRequest)); ASSERT_TRUE(embedded_test_server()->Start()); allow_run_loop_ = std::make_unique(); started_run_loop_ = std::make_unique(); intercept_run_loop_ = std::make_unique(); completed_run_loop_ = std::make_unique(); failed_run_loop_ = std::make_unique(); Tab* tab = shell()->tab(); TabImpl* tab_impl = static_cast(tab); tab_impl->profile()->SetDownloadDelegate(this); auto* browser_context = tab_impl->web_contents()->GetBrowserContext(); auto* download_manager_delegate = browser_context->GetDownloadManager()->GetDelegate(); static_cast(download_manager_delegate) ->set_download_dropped_closure_for_testing(base::BindRepeating( &DownloadBrowserTest::DownloadDropped, base::Unretained(this))); } void WaitForAllow() { allow_run_loop_->Run(); } void WaitForIntercept() { intercept_run_loop_->Run(); } void WaitForStarted() { started_run_loop_->Run(); } void WaitForCompleted() { completed_run_loop_->Run(); } void WaitForFailed() { failed_run_loop_->Run(); } void set_intercept() { intercept_ = true; } void set_disallow() { allow_ = false; } void set_started_callback( base::OnceCallback callback) { started_callback_ = std::move(callback); } void set_failed_callback( base::OnceCallback callback) { failed_callback_ = std::move(callback); } bool started() { return started_; } base::FilePath download_location() { return download_location_; } int64_t total_bytes() { return total_bytes_; } DownloadError download_state() { return download_state_; } std::string mime_type() { return mime_type_; } int completed_count() { return completed_count_; } int failed_count() { return failed_count_; } int download_dropped_count() { return download_dropped_count_; } private: // DownloadDelegate implementation: void AllowDownload(Tab* tab, const GURL& url, const std::string& request_method, absl::optional request_initiator, AllowDownloadCallback callback) override { std::move(callback).Run(allow_); allow_run_loop_->Quit(); } bool InterceptDownload(const GURL& url, const std::string& user_agent, const std::string& content_disposition, const std::string& mime_type, int64_t content_length) override { intercept_run_loop_->Quit(); return intercept_; } void DownloadStarted(Download* download) override { started_ = true; started_run_loop_->Quit(); CHECK_EQ(download->GetState(), DownloadState::kInProgress); if (started_callback_) std::move(started_callback_).Run(download); } void DownloadCompleted(Download* download) override { completed_count_++; download_location_ = download->GetLocation(); total_bytes_ = download->GetTotalBytes(); download_state_ = download->GetError(); mime_type_ = download->GetMimeType(); CHECK_EQ(download->GetReceivedBytes(), total_bytes_); CHECK_EQ(download->GetState(), DownloadState::kComplete); completed_run_loop_->Quit(); } void DownloadFailed(Download* download) override { failed_count_++; download_state_ = download->GetError(); failed_run_loop_->Quit(); if (failed_callback_) std::move(failed_callback_).Run(download); } void DownloadDropped() { download_dropped_count_++; } bool intercept_ = false; bool allow_ = true; bool started_ = false; base::OnceCallback started_callback_; base::OnceCallback failed_callback_; base::FilePath download_location_; int64_t total_bytes_ = 0; DownloadError download_state_ = DownloadError::kNoError; std::string mime_type_; int completed_count_ = 0; int failed_count_ = 0; int download_dropped_count_ = 0; std::unique_ptr allow_run_loop_; std::unique_ptr intercept_run_loop_; std::unique_ptr started_run_loop_; std::unique_ptr completed_run_loop_; std::unique_ptr failed_run_loop_; }; } // namespace // Ensures that if the delegate disallows the downloads then WebLayer // doesn't download it. IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, DisallowNoDownload) { set_disallow(); GURL url(embedded_test_server()->GetURL("/content-disposition.html")); // Downloads always count as failed navigations. TestNavigationObserver observer( url, TestNavigationObserver::NavigationEvent::kFailure, shell()); shell()->tab()->GetNavigationController()->Navigate(url); observer.Wait(); WaitForAllow(); EXPECT_FALSE(started()); EXPECT_EQ(completed_count(), 0); EXPECT_EQ(failed_count(), 0); EXPECT_EQ(download_dropped_count(), 1); } // Ensures that if the delegate chooses to intercept downloads then WebLayer // doesn't download it. IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, InterceptNoDownload) { set_intercept(); GURL url(embedded_test_server()->GetURL("/content-disposition.html")); shell()->tab()->GetNavigationController()->Navigate(url); WaitForIntercept(); EXPECT_FALSE(started()); EXPECT_EQ(completed_count(), 0); EXPECT_EQ(failed_count(), 0); EXPECT_EQ(download_dropped_count(), 1); } IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Basic) { GURL url(embedded_test_server()->GetURL("/content-disposition.html")); shell()->tab()->GetNavigationController()->Navigate(url); WaitForCompleted(); EXPECT_TRUE(started()); EXPECT_EQ(completed_count(), 1); EXPECT_EQ(failed_count(), 0); EXPECT_EQ(download_dropped_count(), 0); EXPECT_EQ(download_state(), DownloadError::kNoError); EXPECT_EQ(mime_type(), "text/html"); // Check that the size on disk matches what's expected. { base::ScopedAllowBlockingForTesting allow_blocking; int64_t downloaded_file_size, original_file_size; EXPECT_TRUE(base::GetFileSize(download_location(), &downloaded_file_size)); base::FilePath test_data_dir; CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir)); EXPECT_TRUE(base::GetFileSize( test_data_dir.Append(base::FilePath( FILE_PATH_LITERAL("weblayer/test/data/content-disposition.html"))), &original_file_size)); EXPECT_EQ(downloaded_file_size, total_bytes()); } // Ensure browser tests don't write to the default machine download directory // to avoid filing it up. EXPECT_NE(BrowserContextImpl::GetDefaultDownloadDirectory(), download_location().DirName()); } // Test consistently failing on android: crbug.com/1273105 #if BUILDFLAG(IS_ANDROID) #define MAYBE_OverrideDownloadDirectory DISABLED_OverrideDownloadDirectory #else #define MAYBE_OverrideDownloadDirectory OverrideDownloadDirectory #endif IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, MAYBE_OverrideDownloadDirectory) { base::ScopedAllowBlockingForTesting allow_blocking; base::ScopedTempDir download_dir; ASSERT_TRUE(download_dir.CreateUniqueTempDir()); TabImpl* tab_impl = static_cast(shell()->tab()); auto* browser_context = tab_impl->web_contents()->GetBrowserContext(); auto* browser_context_impl = static_cast(browser_context); browser_context_impl->profile_impl()->SetDownloadDirectory( download_dir.GetPath()); GURL url(embedded_test_server()->GetURL("/content-disposition.html")); shell()->tab()->GetNavigationController()->Navigate(url); WaitForCompleted(); EXPECT_EQ(completed_count(), 1); EXPECT_EQ(failed_count(), 0); EXPECT_EQ(download_dir.GetPath(), download_location().DirName()); } IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Cancel) { set_started_callback(base::BindLambdaForTesting([&](Download* download) { download->Cancel(); // Also allow the download to complete. GURL url = embedded_test_server()->GetURL( content::SlowDownloadHttpResponse::kFinishSlowResponseUrl); shell()->tab()->GetNavigationController()->Navigate(url); })); set_failed_callback(base::BindLambdaForTesting([](Download* download) { CHECK_EQ(download->GetState(), DownloadState::kCancelled); })); // Create a request that doesn't complete right away to avoid flakiness. GURL url(embedded_test_server()->GetURL( content::SlowDownloadHttpResponse::kKnownSizeUrl)); shell()->tab()->GetNavigationController()->Navigate(url); WaitForFailed(); EXPECT_EQ(completed_count(), 0); EXPECT_EQ(failed_count(), 1); EXPECT_EQ(download_dropped_count(), 0); EXPECT_EQ(download_state(), DownloadError::kCancelled); } // TODO(crbug.com/1314060): Flaky on Windows and Linux. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) #define MAYBE_PauseResume DISABLED_PauseResume #else #define MAYBE_PauseResume PauseResume #endif IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, MAYBE_PauseResume) { // Add an initial navigation to avoid the tab being deleted if the first // navigation is a download, since we use the tab for convenience in the // lambda. OneShotNavigationObserver observer(shell()); shell()->tab()->GetNavigationController()->Navigate(GURL("about:blank")); observer.WaitForNavigation(); set_started_callback(base::BindLambdaForTesting([&](Download* download) { download->Pause(); GURL url = embedded_test_server()->GetURL( content::SlowDownloadHttpResponse::kFinishSlowResponseUrl); base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce( [](Download* download, Shell* shell, const GURL& url) { CHECK_EQ(download->GetState(), DownloadState::kPaused); download->Resume(); // Also allow the download to complete. shell->tab()->GetNavigationController()->Navigate(url); }, download, shell(), url)); })); // Create a request that doesn't complete right away to avoid flakiness. GURL url(embedded_test_server()->GetURL( content::SlowDownloadHttpResponse::kKnownSizeUrl)); shell()->tab()->GetNavigationController()->Navigate(url); WaitForCompleted(); EXPECT_EQ(completed_count(), 1); EXPECT_EQ(failed_count(), 0); EXPECT_EQ(download_dropped_count(), 0); } IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, NetworkError) { set_failed_callback(base::BindLambdaForTesting([](Download* download) { CHECK_EQ(download->GetState(), DownloadState::kFailed); })); // Create a request that doesn't complete right away. GURL url(embedded_test_server()->GetURL( content::SlowDownloadHttpResponse::kKnownSizeUrl)); shell()->tab()->GetNavigationController()->Navigate(url); WaitForStarted(); EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); WaitForFailed(); EXPECT_EQ(completed_count(), 0); EXPECT_EQ(failed_count(), 1); EXPECT_EQ(download_dropped_count(), 0); EXPECT_EQ(download_state(), DownloadError::kConnectivityError); } IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, PendingOnExist) { // Create a request that doesn't complete right away. GURL url(embedded_test_server()->GetURL( content::SlowDownloadHttpResponse::kKnownSizeUrl)); shell()->tab()->GetNavigationController()->Navigate(url); WaitForStarted(); // If this test crashes later then there'd be a regression. } } // namespace weblayer