|  | // Copyright 2018 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/ash/file_manager/file_manager_test_util.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  |  | 
|  | #include "base/files/file_enumerator.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/path_service.h" | 
|  | #include "base/ranges/algorithm.h" | 
|  | #include "base/test/bind.h" | 
|  | #include "chrome/browser/apps/app_service/app_service_proxy_ash.h" | 
|  | #include "chrome/browser/ash/file_manager/app_id.h" | 
|  | #include "chrome/browser/ash/file_manager/file_tasks.h" | 
|  | #include "chrome/browser/ash/file_manager/path_util.h" | 
|  | #include "chrome/browser/ash/file_manager/volume_manager_observer.h" | 
|  | #include "chrome/browser/ash/profiles/profile_helper.h" | 
|  | #include "chrome/browser/extensions/chrome_test_extension_loader.h" | 
|  | #include "chrome/browser/extensions/component_loader.h" | 
|  | #include "chrome/browser/extensions/extension_service.h" | 
|  | #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h" | 
|  | #include "chrome/common/chrome_paths.h" | 
|  | #include "components/services/app_service/public/cpp/intent_test_util.h" | 
|  | #include "extensions/browser/entry_info.h" | 
|  | #include "extensions/browser/extension_system.h" | 
|  | #include "net/base/mime_util.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using platform_util::OpenOperationResult; | 
|  |  | 
|  | namespace file_manager { | 
|  | namespace test { | 
|  |  | 
|  | FolderInMyFiles::FolderInMyFiles(Profile* profile) : profile_(profile) { | 
|  | const base::FilePath root = util::GetMyFilesFolderForProfile(profile); | 
|  | VolumeManager::Get(profile)->RegisterDownloadsDirectoryForTesting(root); | 
|  |  | 
|  | base::ScopedAllowBlockingForTesting allow_blocking; | 
|  | constexpr base::FilePath::CharType kPrefix[] = FILE_PATH_LITERAL("a_folder"); | 
|  | CHECK(CreateTemporaryDirInDir(root, kPrefix, &folder_)); | 
|  | } | 
|  |  | 
|  | FolderInMyFiles::~FolderInMyFiles() = default; | 
|  |  | 
|  | void FolderInMyFiles::Add(const std::vector<base::FilePath>& files) { | 
|  | base::ScopedAllowBlockingForTesting allow_blocking; | 
|  | for (const auto& file : files) { | 
|  | files_.push_back(folder_.Append(file.BaseName())); | 
|  | base::CopyFile(file, files_.back()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FolderInMyFiles::AddWithName(const base::FilePath& file, | 
|  | const base::FilePath& new_base_name) { | 
|  | base::ScopedAllowBlockingForTesting allow_blocking; | 
|  | files_.push_back(folder_.Append(new_base_name)); | 
|  | base::CopyFile(file, files_.back()); | 
|  | } | 
|  |  | 
|  | OpenOperationResult FolderInMyFiles::Open(const base::FilePath& file) { | 
|  | const auto& it = | 
|  | base::ranges::find(files_, file.BaseName(), &base::FilePath::BaseName); | 
|  | EXPECT_FALSE(it == files_.end()); | 
|  | if (it == files_.end()) { | 
|  | return platform_util::OPEN_FAILED_PATH_NOT_FOUND; | 
|  | } | 
|  |  | 
|  | const base::FilePath& path = *it; | 
|  | base::RunLoop run_loop; | 
|  | OpenOperationResult open_result; | 
|  | platform_util::OpenItem( | 
|  | profile_, path, platform_util::OPEN_FILE, | 
|  | base::BindLambdaForTesting([&](OpenOperationResult result) { | 
|  | open_result = result; | 
|  | run_loop.Quit(); | 
|  | })); | 
|  | run_loop.Run(); | 
|  |  | 
|  | return open_result; | 
|  | } | 
|  |  | 
|  | void FolderInMyFiles::Refresh() { | 
|  | constexpr bool kRecursive = false; | 
|  | files_.clear(); | 
|  |  | 
|  | base::ScopedAllowBlockingForTesting allow_blocking; | 
|  | base::FileEnumerator e(folder_, kRecursive, base::FileEnumerator::FILES); | 
|  | for (base::FilePath path = e.Next(); !path.empty(); path = e.Next()) { | 
|  | files_.push_back(path); | 
|  | } | 
|  |  | 
|  | std::sort(files_.begin(), files_.end(), | 
|  | [](const base::FilePath& l, const base::FilePath& r) { | 
|  | return l.BaseName() < r.BaseName(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void AddDefaultComponentExtensionsOnMainThread(Profile* profile) { | 
|  | CHECK(profile); | 
|  |  | 
|  | extensions::ComponentLoader::EnableBackgroundExtensionsForTesting(); | 
|  | extensions::ExtensionService* service = | 
|  | extensions::ExtensionSystem::Get(profile)->extension_service(); | 
|  | service->component_loader()->AddDefaultComponentExtensions(false); | 
|  | // AddDefaultComponentExtensions() is normally invoked during | 
|  | // ExtensionService::Init() which also invokes UninstallMigratedExtensions(). | 
|  | // Invoke it here as well, otherwise migrated extensions will remain installed | 
|  | // for the duration of the test. Note this may result in immediately | 
|  | // uninstalling an extension just installed above. | 
|  | service->UninstallMigratedExtensionsForTest(); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | // Helper to exit a RunLoop the next time OnVolumeMounted() is invoked. | 
|  | class VolumeWaiter : public VolumeManagerObserver { | 
|  | public: | 
|  | VolumeWaiter(Profile* profile, const base::RepeatingClosure& on_mount) | 
|  | : profile_(profile), on_mount_(on_mount) { | 
|  | VolumeManager::Get(profile_)->AddObserver(this); | 
|  | } | 
|  |  | 
|  | ~VolumeWaiter() override { | 
|  | VolumeManager::Get(profile_)->RemoveObserver(this); | 
|  | } | 
|  |  | 
|  | void OnVolumeMounted(ash::MountError error_code, | 
|  | const Volume& volume) override { | 
|  | on_mount_.Run(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | raw_ptr<Profile, ExperimentalAsh> profile_; | 
|  | base::RepeatingClosure on_mount_; | 
|  | }; | 
|  | }  // namespace | 
|  |  | 
|  | scoped_refptr<const extensions::Extension> InstallTestingChromeApp( | 
|  | Profile* profile, | 
|  | const char* test_path_ascii) { | 
|  | base::ScopedAllowBlockingForTesting allow_io; | 
|  | extensions::ChromeTestExtensionLoader loader(profile); | 
|  |  | 
|  | base::FilePath path; | 
|  | CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &path)); | 
|  | path = path.AppendASCII(test_path_ascii); | 
|  |  | 
|  | // ChromeTestExtensionLoader waits for the background page to load before | 
|  | // returning. | 
|  | auto extension = loader.LoadExtension(path); | 
|  | CHECK(extension); | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | base::WeakPtr<file_manager::Volume> InstallFileSystemProviderChromeApp( | 
|  | Profile* profile) { | 
|  | static constexpr char kFileSystemProviderFilesystemId[] = | 
|  | "test-image-provider-fs"; | 
|  | base::RunLoop run_loop; | 
|  |  | 
|  | // Incognito profiles don't have VolumeManager events (so a | 
|  | // file_manager::test::VolumeWaiter won't work with them). Switch to the | 
|  | // original profile in that case. For regular (not incognito) profiles, | 
|  | // p->GetOriginalProfile() returns p. | 
|  | Profile* profile_with_volume_manager_events = profile->GetOriginalProfile(); | 
|  |  | 
|  | file_manager::test::VolumeWaiter waiter(profile_with_volume_manager_events, | 
|  | run_loop.QuitClosure()); | 
|  | auto extension = InstallTestingChromeApp( | 
|  | profile, "extensions/api_test/file_browser/image_provider"); | 
|  | run_loop.Run(); | 
|  |  | 
|  | auto* volume_manager = | 
|  | file_manager::VolumeManager::Get(profile_with_volume_manager_events); | 
|  | CHECK(volume_manager); | 
|  | base::WeakPtr<file_manager::Volume> volume; | 
|  | for (auto& v : volume_manager->GetVolumeList()) { | 
|  | if (v->file_system_id() == kFileSystemProviderFilesystemId) { | 
|  | volume = v; | 
|  | } | 
|  | } | 
|  |  | 
|  | CHECK(volume); | 
|  | return volume; | 
|  | } | 
|  |  | 
|  | std::vector<file_tasks::FullTaskDescriptor> GetTasksForFile( | 
|  | Profile* profile, | 
|  | const base::FilePath& file) { | 
|  | std::string mime_type; | 
|  | net::GetMimeTypeFromFile(file, &mime_type); | 
|  | CHECK(!mime_type.empty()); | 
|  |  | 
|  | std::vector<extensions::EntryInfo> entries; | 
|  | entries.emplace_back(file, mime_type, false); | 
|  |  | 
|  | // Use fake URLs in this helper. This is needed because FindAppServiceTasks | 
|  | // uses the file extension found in the URL to do file extension matching. | 
|  | std::vector<GURL> file_urls; | 
|  | GURL url = GURL(base::JoinString( | 
|  | {"filesystem:https://site.com/isolated/foo", file.Extension()}, "")); | 
|  | CHECK(url.is_valid()); | 
|  | file_urls.emplace_back(url); | 
|  |  | 
|  | std::vector<file_tasks::FullTaskDescriptor> result; | 
|  | bool invoked_synchronously = false; | 
|  | auto callback = base::BindLambdaForTesting( | 
|  | [&](std::unique_ptr<file_tasks::ResultingTasks> resulting_tasks) { | 
|  | result = std::move(resulting_tasks->tasks); | 
|  | invoked_synchronously = true; | 
|  | }); | 
|  |  | 
|  | FindAllTypesOfTasks(profile, entries, file_urls, {""}, callback); | 
|  |  | 
|  | // MIME sniffing requires a run loop, but the mime type must be explicitly | 
|  | // available, and is provided in this helper. | 
|  | CHECK(invoked_synchronously); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void AddFakeAppWithIntentFilters( | 
|  | const std::string& app_id, | 
|  | std::vector<apps::IntentFilterPtr> intent_filters, | 
|  | apps::AppType app_type, | 
|  | absl::optional<bool> handles_intents, | 
|  | apps::AppServiceProxy* app_service_proxy) { | 
|  | std::vector<apps::AppPtr> apps; | 
|  | auto app = std::make_unique<apps::App>(app_type, app_id); | 
|  | app->app_id = app_id; | 
|  | app->app_type = app_type; | 
|  | app->handles_intents = handles_intents; | 
|  | app->readiness = apps::Readiness::kReady; | 
|  | app->intent_filters = std::move(intent_filters); | 
|  | apps.push_back(std::move(app)); | 
|  | app_service_proxy->AppRegistryCache().OnApps( | 
|  | std::move(apps), app_type, false /* should_notify_initialized */); | 
|  | } | 
|  |  | 
|  | void AddFakeWebApp(const std::string& app_id, | 
|  | const std::string& mime_type, | 
|  | const std::string& file_extension, | 
|  | const std::string& activity_label, | 
|  | absl::optional<bool> handles_intents, | 
|  | apps::AppServiceProxy* app_service_proxy) { | 
|  | std::vector<apps::IntentFilterPtr> filters; | 
|  | filters.push_back(apps_util::MakeFileFilterForView(mime_type, file_extension, | 
|  | activity_label)); | 
|  | AddFakeAppWithIntentFilters(app_id, std::move(filters), apps::AppType::kWeb, | 
|  | handles_intents, app_service_proxy); | 
|  | } | 
|  |  | 
|  | }  // namespace test | 
|  | }  // namespace file_manager |