blob: 591ee9d5abcc76498a6f86a984eabb72b351e4e1 [file] [log] [blame]
// Copyright 2018 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/ash/file_manager/file_manager_test_util.h"
#include "ash/constants/ash_features.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.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/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/web_applications/system_web_app_ui_utils.h"
#include "chrome/common/chrome_paths.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 = std::find_if(files_.begin(), files_.end(),
[file](const base::FilePath& i) {
return i.BaseName() == file.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();
// On ChromeOS, the OpenOperationResult is determined in
// OpenFileMimeTypeAfterTasksListed() which also invokes
// ExecuteFileTaskForUrl(). For WebApps like chrome://media-app, that invokes
// WebApps::LaunchAppWithFiles() via AppServiceProxy.
// Depending how the mime type of |path| is determined (e.g. extension,
// metadata sniffing), there may be a number of asynchronous steps involved
// before the call to ExecuteFileTaskForUrl(). After that, the OpenItem
// callback is invoked, which exits the RunLoop above.
// That used to be enough to also launch a Browser for the WebApp. However,
// since https://crrev.com/c/2121860, ExecuteFileTaskForUrl() goes through the
// mojoAppService, so it's necessary to flush those calls for WebApps to open.
web_app::FlushSystemWebAppLaunchesForTesting(profile_);
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();
if (!ash::features::IsFileManagerSwaEnabled()) {
// The File Manager component extension should have been added for loading
// into the user profile, but not into the sign-in profile.
CHECK(extensions::ExtensionSystem::Get(profile)
->extension_service()
->component_loader()
->Exists(kFileManagerAppId));
CHECK(!extensions::ExtensionSystem::Get(
ash::ProfileHelper::GetSigninProfile())
->extension_service()
->component_loader()
->Exists(kFileManagerAppId));
}
}
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(chromeos::MountError error_code,
const Volume& volume) override {
on_mount_.Run();
}
private:
Profile* 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;
file_manager::test::VolumeWaiter waiter(profile, 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);
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<std::vector<file_tasks::FullTaskDescriptor>> tasks) {
result = *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;
}
} // namespace test
} // namespace file_manager