blob: 7c4da5885a2ecf4ff9e326d557aa16ceebc00e2b [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/test/run_until.h"
#include "base/test/test_future.h"
#include "base/test/with_feature_override.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_test_file_activity_observer.h"
#include "chrome/browser/pdf/pdf_extension_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/file_system_chooser_test_helpers.h"
#include "content/public/test/hit_test_region_observer.h"
#include "pdf/pdf_features.h"
#include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/views/views_switches.h"
namespace {
// Saved file is compared with the expectation after this delay.
constexpr base::TimeDelta kFileComparisonDelay = base::Seconds(1);
// LINT.IfChange(MaxSaveBufferSize)
constexpr uint32_t kMaxSaveBufferSize = 16 * 1000 * 1000;
// LINT.ThenChange(//pdf/pdf_view_web_plugin.cc:MaxSaveBufferSize)
// Filepath used to generate a PDF larger than `kMaxSaveBufferSize` in memory.
constexpr std::string_view kLargeTestCase = "/large_in_memory.pdf";
// Simulate saving the PDF from the context menu "Save As...".
void SimulateContextMenuSaveAs(content::RenderFrameHost* extension_host,
const GURL& url) {
content::ContextMenuParams context_menu_params;
context_menu_params.media_type =
blink::mojom::ContextMenuDataMediaType::kPlugin;
context_menu_params.src_url = url;
context_menu_params.page_url = url;
content::WaitForHitTestData(extension_host);
TestRenderViewContextMenu menu(*extension_host, context_menu_params);
menu.Init();
menu.ExecuteCommand(IDC_SAVE_PAGE, 0);
}
// Trigger toolbar download button.
void TriggerDownloadButton(content::RenderFrameHost* extension_host) {
ASSERT_TRUE(content::ExecJs(
extension_host,
"var viewer = document.getElementById('viewer');"
"var toolbar = viewer.shadowRoot.getElementById('toolbar');"
"var downloads = toolbar.shadowRoot.getElementById('downloads');"
"downloads.shadowRoot.getElementById('save').click();"));
}
// Compares content of the saved file and original test file. This function may
// be called while saving is not finished yet. On Windows trying to open the
// file for reading while it is open for writing may result in a failure in
// closing the file after save is finished. Hence file checking is done with a
// delay to reduce the possibility of flakiness, and is repeated for the rare
// cases that saving takes more than the 1s delay.
void CompareFileContent(const base::FilePath& test_file_path,
const base::FilePath& save_path,
base::OnceCallback<void(void)> callback,
bool wait_before_compare) {
if (!wait_before_compare && base::ContentsEqual(test_file_path, save_path)) {
std::move(callback).Run();
return;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CompareFileContent, test_file_path, save_path,
std::move(callback), /*wait_before_compare=*/false),
kFileComparisonDelay);
}
// Generates a PDF larger than kMaxSaveBufferSize bytes.
std::string GetLargePDFContent() {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath test_file_path = ui_test_utils::GetTestFilePath(
base::FilePath(FILE_PATH_LITERAL("pdf")),
base::FilePath(FILE_PATH_LITERAL("test.pdf")));
std::string content;
EXPECT_TRUE(base::ReadFileToString(test_file_path, &content));
content.resize(kMaxSaveBufferSize + 1, ' ');
return content;
}
} // namespace
// TODO(crbug.com/394111292): Remove this test when `PdfGetSaveDataInBlocks`
// launches.
class PDFExtensionDownloadTest : public base::test::WithFeatureOverride,
public PDFExtensionTestBase {
public:
PDFExtensionDownloadTest()
: base::test::WithFeatureOverride(chrome_pdf::features::kPdfOopif) {}
std::vector<base::test::FeatureRef> GetDisabledFeatures() const override {
std::vector<base::test::FeatureRef> disabled =
PDFExtensionTestBase::GetDisabledFeatures();
disabled.push_back(chrome_pdf::features::kPdfGetSaveDataInBlocks);
return disabled;
}
void SetUpCommandLine(base::CommandLine* command_line) override {
PDFExtensionTestBase::SetUpCommandLine(command_line);
// Clicks from tests should always be allowed, even on dialogs that have
// protection against accidental double-clicking/etc.
command_line->AppendSwitch(
views::switches::kDisableInputEventActivationProtectionForTesting);
}
void SetUpOnMainThread() override {
PDFExtensionTestBase::SetUpOnMainThread();
browser()->profile()->GetPrefs()->SetBoolean(prefs::kPromptForDownload,
false);
DownloadPrefs::FromDownloadManager(GetDownloadManager())
->ResetAutoOpenByUser();
file_activity_observer_ =
std::make_unique<DownloadTestFileActivityObserver>(
browser()->profile());
file_activity_observer_->EnableFileChooser(true);
}
void TearDownOnMainThread() override {
// Needs to be torn down on the main thread. `file_activity_observer_` holds
// a reference to the ChromeDownloadManagerDelegate which should be
// destroyed on the UI thread.
file_activity_observer_.reset();
PDFExtensionTestBase::TearDownOnMainThread();
}
bool UseOopif() const override { return GetParam(); }
void SavePdfFromExtensionFrameContextMenu(
content::RenderFrameHost* extension_host,
const GURL& url) {
auto download_waiter = CreateDownloadWaiter();
SimulateContextMenuSaveAs(extension_host, url);
WaitForDownload(download_waiter.get());
}
void SavePdfWithDownloadButton(content::RenderFrameHost* extension_host) {
auto download_waiter = CreateDownloadWaiter();
TriggerDownloadButton(extension_host);
WaitForDownload(download_waiter.get());
}
void CheckDownloadedFile(const base::FilePath::StringType& expected_name) {
content::DownloadManager::DownloadVector downloads;
GetDownloadManager()->GetAllDownloads(&downloads);
ASSERT_EQ(1u, downloads.size());
EXPECT_EQ(expected_name,
downloads[0]->GetTargetFilePath().BaseName().value());
}
private:
content::DownloadManager* GetDownloadManager() {
return browser()->profile()->GetDownloadManager();
}
std::unique_ptr<content::DownloadTestObserver> CreateDownloadWaiter() {
return std::make_unique<content::DownloadTestObserverTerminal>(
GetDownloadManager(), /*wait_count=*/1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
}
void WaitForDownload(content::DownloadTestObserver* download_waiter) {
download_waiter->WaitForFinished();
EXPECT_EQ(1u, download_waiter->NumDownloadsSeenInState(
download::DownloadItem::COMPLETE));
}
std::unique_ptr<DownloadTestFileActivityObserver> file_activity_observer_;
};
IN_PROC_BROWSER_TEST_P(PDFExtensionDownloadTest, BasicUsingContextMenu) {
const GURL url = embedded_test_server()->GetURL("/pdf/test.pdf");
content::RenderFrameHost* extension_host = LoadPdfGetExtensionHost(url);
ASSERT_TRUE(extension_host);
SavePdfFromExtensionFrameContextMenu(extension_host, url);
CheckDownloadedFile(FILE_PATH_LITERAL("test.pdf"));
}
IN_PROC_BROWSER_TEST_P(PDFExtensionDownloadTest, BasicUsingDownloadButton) {
const GURL url = embedded_test_server()->GetURL("/pdf/test.pdf");
content::RenderFrameHost* extension_host = LoadPdfGetExtensionHost(url);
ASSERT_TRUE(extension_host);
SavePdfWithDownloadButton(extension_host);
CheckDownloadedFile(FILE_PATH_LITERAL("test.pdf"));
}
// TODO(crbug.com/40268279): Stop testing both modes after OOPIF PDF viewer
// launches.
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(PDFExtensionDownloadTest);
class PDFExtensionSaveInBlocksTest : public base::test::WithFeatureOverride,
public PDFExtensionTestBase {
public:
PDFExtensionSaveInBlocksTest()
: base::test::WithFeatureOverride(chrome_pdf::features::kPdfOopif) {}
std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures()
const override {
std::vector<base::test::FeatureRefAndParams> enabled =
PDFExtensionTestBase::GetEnabledFeatures();
enabled.push_back({chrome_pdf::features::kPdfGetSaveDataInBlocks, {}});
// "PdfGetSaveDataInBlocks" needs "PdfUseShowSaveFilePicker".
enabled.push_back({chrome_pdf::features::kPdfUseShowSaveFilePicker, {}});
return enabled;
}
// PDFExtensionTestBase:
void SetUpInProcessBrowserTestFixture() override {
// Set up the policy provider to allow the file picker to run inside PDF
// viewer's extension.
SetUpPolicyProvider();
SetPolicy();
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
}
void SetUpPolicyProvider() {
policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
}
void SetPolicy() {
policy::PolicyMap policy_map;
base::Value::List allowed_origins;
allowed_origins.Append(
base::FilePath(ChromeContentClient::kPDFExtensionPluginPath)
.MaybeAsASCII());
policy_map.Set(
policy::key::kFileOrDirectoryPickerWithoutGestureAllowedForOrigins,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
policy::POLICY_SOURCE_PLATFORM, base::Value(std::move(allowed_origins)),
nullptr);
policy_provider_.UpdateChromePolicy(policy_map);
}
private:
testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
};
IN_PROC_BROWSER_TEST_P(PDFExtensionSaveInBlocksTest, BasicUsingContextMenu) {
const GURL url = embedded_test_server()->GetURL("/pdf/test.pdf");
content::RenderFrameHost* extension_host = LoadPdfGetExtensionHost(url);
ASSERT_TRUE(extension_host);
// Setup fake file selection reply.
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath save_path = temp_dir.GetPath().AppendASCII("test.pdf");
ui::SelectFileDialog::SetFactory(
std::make_unique<content::FakeSelectFileDialogFactory>(
std::vector<base::FilePath>{save_path}));
SimulateContextMenuSaveAs(extension_host, url);
base::FilePath test_file_path = ui_test_utils::GetTestFilePath(
base::FilePath(FILE_PATH_LITERAL("pdf")),
base::FilePath(FILE_PATH_LITERAL("test.pdf")));
base::test::TestFuture<void> future;
CompareFileContent(test_file_path, save_path, future.GetCallback(),
/*wait_before_compare=*/true);
ASSERT_TRUE(future.Wait());
}
IN_PROC_BROWSER_TEST_P(PDFExtensionSaveInBlocksTest, BasicUsingDownloadButton) {
const GURL url = embedded_test_server()->GetURL("/pdf/test.pdf");
content::RenderFrameHost* extension_host = LoadPdfGetExtensionHost(url);
ASSERT_TRUE(extension_host);
// Setup fake file selection reply.
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath save_path = temp_dir.GetPath().AppendASCII("test.pdf");
ui::SelectFileDialog::SetFactory(
std::make_unique<content::FakeSelectFileDialogFactory>(
std::vector<base::FilePath>{save_path}));
TriggerDownloadButton(extension_host);
base::FilePath test_file_path = ui_test_utils::GetTestFilePath(
base::FilePath(FILE_PATH_LITERAL("pdf")),
base::FilePath(FILE_PATH_LITERAL("test.pdf")));
base::test::TestFuture<void> future;
CompareFileContent(test_file_path, save_path, future.GetCallback(),
/*wait_before_compare=*/true);
ASSERT_TRUE(future.Wait());
}
// Tests with an in memory generated PDF larger than `kMaxSaveBufferSize` bytes.
class PDFExtensionSaveInBlocksLargeFileTest
: public PDFExtensionSaveInBlocksTest {
public:
PDFExtensionSaveInBlocksLargeFileTest() = default;
// PDFExtensionTestBase:
void RegisterTestServerRequestHandler() override {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
[](const net::test_server::HttpRequest& request)
-> std::unique_ptr<net::test_server::HttpResponse> {
if (request.relative_url != kLargeTestCase) {
return nullptr;
}
auto response =
std::make_unique<net::test_server::BasicHttpResponse>();
response->set_content(GetLargePDFContent());
return response;
}));
}
};
IN_PROC_BROWSER_TEST_P(PDFExtensionSaveInBlocksLargeFileTest,
UsingDownloadButton) {
const GURL url = embedded_test_server()->GetURL(kLargeTestCase);
content::RenderFrameHost* extension_host = LoadPdfGetExtensionHost(url);
ASSERT_TRUE(extension_host);
// Setup fake file selection reply.
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath save_path = temp_dir.GetPath().AppendASCII("test.pdf");
ui::SelectFileDialog::SetFactory(
std::make_unique<content::FakeSelectFileDialogFactory>(
std::vector<base::FilePath>{save_path}));
TriggerDownloadButton(extension_host);
base::FilePath expected_path = temp_dir.GetPath().AppendASCII("expected.pdf");
EXPECT_TRUE(base::WriteFile(expected_path, GetLargePDFContent()));
base::test::TestFuture<void> future;
CompareFileContent(expected_path, save_path, future.GetCallback(),
/*wait_before_compare=*/true);
ASSERT_TRUE(future.Wait());
}
// TODO(crbug.com/40268279): Stop testing both modes after OOPIF PDF viewer
// launches.
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(PDFExtensionSaveInBlocksTest);
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(PDFExtensionSaveInBlocksLargeFileTest);