| // 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 "chrome/browser/ui/ash/thumbnail_loader.h" |
| |
| #include <memory> |
| |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/path_service.h" |
| #include "base/test/bind.h" |
| #include "chrome/browser/ash/file_manager/app_id.h" |
| #include "chrome/browser/ash/file_manager/fileapi_util.h" |
| #include "chrome/browser/ash/file_manager/open_util.h" |
| #include "chrome/browser/ash/file_manager/volume_manager.h" |
| #include "chrome/browser/extensions/component_loader.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "content/public/test/browser_test.h" |
| #include "extensions/common/extension.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| // References to paths set up in the test mount point during the browser test |
| // setup. |
| enum class TestPath { kNonExistent, kEmptyDir, kJpg, kBrokenJpg, kPng, kBin }; |
| |
| // Copies |bitmap| into |copy| and runs |callback|. |
| void CopyBitmapAndRunClosure(base::OnceClosure callback, |
| SkBitmap* copy, |
| const SkBitmap* bitmap, |
| base::File::Error error) { |
| if (bitmap) { |
| EXPECT_EQ(base::File::FILE_OK, error); |
| *copy = *bitmap; |
| } else { |
| EXPECT_NE(base::File::FILE_OK, error); |
| ADD_FAILURE() << "Got null bitmap"; |
| } |
| std::move(callback).Run(); |
| } |
| |
| // Utility class that registers an external file system mount point, and grants |
| // file manager app access permission for the mount point. |
| class ScopedExternalMountPoint { |
| public: |
| ScopedExternalMountPoint(Profile* profile, const std::string& name) |
| : name_(name) { |
| if (!temp_dir_.CreateUniqueTempDir()) |
| return; |
| |
| storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( |
| name_, storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), |
| temp_dir_.GetPath()); |
| GURL image_loader_url = extensions::Extension::GetBaseURLFromExtensionId( |
| file_manager::kImageLoaderExtensionId); |
| file_manager::util::GetFileSystemContextForSourceURL(profile, |
| image_loader_url) |
| ->external_backend() |
| ->GrantFileAccessToOrigin(url::Origin::Create(image_loader_url), |
| base::FilePath(name_)); |
| } |
| |
| ~ScopedExternalMountPoint() { |
| storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(name_); |
| } |
| |
| bool IsValid() const { return temp_dir_.IsValid(); } |
| const base::FilePath& GetRootPath() const { return temp_dir_.GetPath(); } |
| const std::string& name() const { return name_; } |
| |
| private: |
| base::ScopedTempDir temp_dir_; |
| std::string name_; |
| }; |
| |
| } // namespace |
| |
| class ThumbnailLoaderTest : public InProcessBrowserTest { |
| public: |
| ThumbnailLoaderTest() = default; |
| ThumbnailLoaderTest(const ThumbnailLoaderTest&) = delete; |
| ThumbnailLoaderTest& operator=(const ThumbnailLoaderTest&) = delete; |
| ~ThumbnailLoaderTest() override = default; |
| |
| // InProcessBrowserTest: |
| void SetUpInProcessBrowserTestFixture() override { |
| InProcessBrowserTest::SetUpInProcessBrowserTestFixture(); |
| extensions::ComponentLoader::EnableBackgroundExtensionsForTesting(); |
| } |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| test_mount_point_ = std::make_unique<ScopedExternalMountPoint>( |
| browser()->profile(), "test_downloads"); |
| thumbnail_loader_ = |
| std::make_unique<ash::ThumbnailLoader>(browser()->profile()); |
| ASSERT_TRUE(test_mount_point_->IsValid()); |
| SetUpTestDirStructure(); |
| } |
| void TearDownOnMainThread() override { |
| test_mount_point_.reset(); |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| ash::ThumbnailLoader* GetThumbnailLoader() { return thumbnail_loader_.get(); } |
| |
| base::FilePath GetTestDataFilePath(const std::string& file_name) { |
| // Get the path to file manager's test data directory. |
| base::FilePath source_dir; |
| CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir)); |
| auto test_data_dir = source_dir.AppendASCII("chrome") |
| .AppendASCII("test") |
| .AppendASCII("data") |
| .AppendASCII("chromeos") |
| .AppendASCII("file_manager"); |
| // Return full test data path to the given |file_name|. |
| return test_data_dir.AppendASCII(file_name); |
| } |
| |
| base::FilePath GetTestPath(TestPath test_path) { |
| switch (test_path) { |
| case TestPath::kNonExistent: |
| return mount_point()->GetRootPath().AppendASCII("fake.png"); |
| case TestPath::kEmptyDir: |
| return mount_point()->GetRootPath().AppendASCII("empty_dir"); |
| case TestPath::kJpg: |
| return mount_point()->GetRootPath().AppendASCII("image3.jpg"); |
| case TestPath::kBrokenJpg: |
| return mount_point()->GetRootPath().AppendASCII("broken.jpg"); |
| case TestPath::kPng: |
| return mount_point()->GetRootPath().AppendASCII("image.png"); |
| case TestPath::kBin: |
| return mount_point()->GetRootPath().AppendASCII("random.bin"); |
| } |
| } |
| |
| const ScopedExternalMountPoint* mount_point() const { |
| return test_mount_point_.get(); |
| } |
| |
| private: |
| void SetUpTestDirStructure() { |
| ASSERT_TRUE(base::CreateDirectory(GetTestPath(TestPath::kEmptyDir))); |
| ASSERT_TRUE(base::CopyFile(GetTestDataFilePath("image.png"), |
| GetTestPath(TestPath::kPng))); |
| ASSERT_TRUE(base::CopyFile(GetTestDataFilePath("image3.jpg"), |
| GetTestPath(TestPath::kJpg))); |
| ASSERT_TRUE(base::CopyFile(GetTestDataFilePath("broken.jpg"), |
| GetTestPath(TestPath::kBrokenJpg))); |
| ASSERT_TRUE(base::CopyFile(GetTestDataFilePath("random.bin"), |
| GetTestPath(TestPath::kBin))); |
| } |
| |
| std::unique_ptr<ScopedExternalMountPoint> test_mount_point_; |
| |
| std::unique_ptr<ash::ThumbnailLoader> thumbnail_loader_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, LoadNonExistentFile) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| base::RunLoop run_loop; |
| ash::ThumbnailLoader::ThumbnailRequest request( |
| GetTestPath(TestPath::kNonExistent), gfx::Size(48, 48)); |
| loader->Load(request, |
| base::BindLambdaForTesting([&run_loop](const SkBitmap* bitmap, |
| base::File::Error error) { |
| EXPECT_FALSE(bitmap); |
| EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, error); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, LoadFolder) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| base::RunLoop run_loop; |
| ash::ThumbnailLoader::ThumbnailRequest request( |
| GetTestPath(TestPath::kEmptyDir), gfx::Size(48, 48)); |
| loader->Load(request, |
| base::BindLambdaForTesting([&run_loop](const SkBitmap* bitmap, |
| base::File::Error error) { |
| EXPECT_FALSE(bitmap); |
| EXPECT_EQ(base::File::FILE_ERROR_NOT_A_FILE, error); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, LoadJpg) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| SkBitmap bitmap; |
| base::RunLoop run_loop; |
| ash::ThumbnailLoader::ThumbnailRequest request(GetTestPath(TestPath::kJpg), |
| gfx::Size(48, 48)); |
| loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop.QuitClosure(), &bitmap)); |
| run_loop.Run(); |
| |
| EXPECT_FALSE(bitmap.isNull()); |
| EXPECT_EQ(48, bitmap.width()); |
| EXPECT_EQ(48, bitmap.height()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, LoadBrokenJpg) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| base::RunLoop run_loop; |
| ash::ThumbnailLoader::ThumbnailRequest request( |
| GetTestPath(TestPath::kBrokenJpg), gfx::Size(48, 48)); |
| loader->Load(request, |
| base::BindLambdaForTesting([&run_loop](const SkBitmap* bitmap, |
| base::File::Error error) { |
| EXPECT_FALSE(bitmap); |
| EXPECT_EQ(base::File::FILE_ERROR_FAILED, error); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, LoadPng) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| SkBitmap bitmap; |
| base::RunLoop run_loop; |
| ash::ThumbnailLoader::ThumbnailRequest request(GetTestPath(TestPath::kPng), |
| gfx::Size(48, 48)); |
| loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop.QuitClosure(), &bitmap)); |
| run_loop.Run(); |
| EXPECT_FALSE(bitmap.isNull()); |
| EXPECT_EQ(48, bitmap.width()); |
| EXPECT_EQ(48, bitmap.height()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, LoadUnsupportedFiletype) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| ash::ThumbnailLoader::ThumbnailRequest request(GetTestPath(TestPath::kBin), |
| gfx::Size(48, 48)); |
| |
| base::RunLoop run_loop; |
| loader->Load(request, |
| base::BindLambdaForTesting( |
| [&](const SkBitmap* bitmap, base::File::Error error) { |
| EXPECT_FALSE(bitmap); |
| EXPECT_EQ(error, base::File::FILE_ERROR_ABORT); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, RepeatedLoads) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| ash::ThumbnailLoader::ThumbnailRequest request(GetTestPath(TestPath::kPng), |
| gfx::Size(48, 48)); |
| |
| SkBitmap bitmap1; |
| base::RunLoop run_loop1; |
| loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop1.QuitClosure(), &bitmap1)); |
| run_loop1.Run(); |
| ASSERT_FALSE(bitmap1.isNull()); |
| |
| SkBitmap bitmap2; |
| base::RunLoop run_loop2; |
| loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop2.QuitClosure(), &bitmap2)); |
| run_loop2.Run(); |
| ASSERT_FALSE(bitmap2.isNull()); |
| |
| EXPECT_TRUE(gfx::test::AreBitmapsEqual(bitmap1, bitmap2)); |
| |
| // Change the backing image, and verify the loaded bitmap changes, too. |
| { |
| base::ScopedAllowBlockingForTesting allow_io; |
| ASSERT_TRUE(base::CopyFile(GetTestPath(TestPath::kJpg), |
| GetTestPath(TestPath::kPng))); |
| } |
| |
| SkBitmap bitmap3; |
| base::RunLoop run_loop3; |
| loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop3.QuitClosure(), &bitmap3)); |
| run_loop3.Run(); |
| ASSERT_FALSE(bitmap3.isNull()); |
| |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual(bitmap1, bitmap3)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ThumbnailLoaderTest, ConcurrentLoads) { |
| ash::ThumbnailLoader* loader = GetThumbnailLoader(); |
| ASSERT_TRUE(loader); |
| |
| SkBitmap bitmap1; |
| ash::ThumbnailLoader::ThumbnailRequest request1(GetTestPath(TestPath::kPng), |
| gfx::Size(48, 48)); |
| base::RunLoop run_loop1; |
| loader->Load(request1, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop1.QuitClosure(), &bitmap1)); |
| |
| SkBitmap bitmap2; |
| ash::ThumbnailLoader::ThumbnailRequest request2(GetTestPath(TestPath::kPng), |
| gfx::Size(96, 96)); |
| base::RunLoop run_loop2; |
| loader->Load(request2, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop2.QuitClosure(), &bitmap2)); |
| |
| SkBitmap bitmap3; |
| base::RunLoop run_loop3; |
| ash::ThumbnailLoader::ThumbnailRequest request3(GetTestPath(TestPath::kJpg), |
| gfx::Size(48, 48)); |
| loader->Load(request3, base::BindOnce(&CopyBitmapAndRunClosure, |
| run_loop3.QuitClosure(), &bitmap3)); |
| |
| run_loop1.Run(); |
| EXPECT_FALSE(bitmap1.isNull()); |
| EXPECT_EQ(48, bitmap1.width()); |
| EXPECT_EQ(48, bitmap1.height()); |
| |
| run_loop2.Run(); |
| EXPECT_FALSE(bitmap2.isNull()); |
| EXPECT_EQ(96, bitmap2.width()); |
| EXPECT_EQ(96, bitmap2.height()); |
| |
| run_loop3.Run(); |
| EXPECT_FALSE(bitmap3.isNull()); |
| EXPECT_EQ(48, bitmap3.width()); |
| EXPECT_EQ(48, bitmap3.height()); |
| |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual(bitmap1, bitmap2)); |
| EXPECT_FALSE(gfx::test::AreBitmapsEqual(bitmap1, bitmap3)); |
| } |