blob: 8fc730d432a25a16a4a90abc565491f2c95e4cfe [file] [log] [blame]
// 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/task/post_task.h"
#include "base/test/bind.h"
#include "base/threading/thread_restrictions.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<base::RunLoop>();
started_run_loop_ = std::make_unique<base::RunLoop>();
intercept_run_loop_ = std::make_unique<base::RunLoop>();
completed_run_loop_ = std::make_unique<base::RunLoop>();
failed_run_loop_ = std::make_unique<base::RunLoop>();
Tab* tab = shell()->tab();
TabImpl* tab_impl = static_cast<TabImpl*>(tab);
tab_impl->profile()->SetDownloadDelegate(this);
auto* browser_context = tab_impl->web_contents()->GetBrowserContext();
auto* download_manager_delegate =
content::BrowserContext::GetDownloadManager(browser_context)
->GetDelegate();
static_cast<DownloadManagerDelegateImpl*>(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<void(Download* download)> callback) {
started_callback_ = std::move(callback);
}
void set_failed_callback(
base::OnceCallback<void(Download* download)> 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,
base::Optional<url::Origin> 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<void(Download* download)> started_callback_;
base::OnceCallback<void(Download* download)> 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<base::RunLoop> allow_run_loop_;
std::unique_ptr<base::RunLoop> intercept_run_loop_;
std::unique_ptr<base::RunLoop> started_run_loop_;
std::unique_ptr<base::RunLoop> completed_run_loop_;
std::unique_ptr<base::RunLoop> 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());
}
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, OverrideDownloadDirectory) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir download_dir;
ASSERT_TRUE(download_dir.CreateUniqueTempDir());
TabImpl* tab_impl = static_cast<TabImpl*>(shell()->tab());
auto* browser_context = tab_impl->web_contents()->GetBrowserContext();
auto* browser_context_impl =
static_cast<BrowserContextImpl*>(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);
}
IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, 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