Commit da7d9ba5 authored by Toni Barzic's avatar Toni Barzic Committed by Chromium LUCI CQ

Reland "Shutdown holding space delegates while device is suspended"

This is a reland of 2a07cc57

Original change's description:
> Shutdown holding space delegates while device is suspended
>
> Some file systems are unmounted during device suspend (for example drive
> FS), which was causing the holding space file system delegate's file
> path removal detection to remove them from the holding space on device
> suspend. To avoid this issue, holding space keyed service now observes
> device suspend status and shuts down holding space delegates when the
> device suspends. The delegates are re-initialized when suspend ends,
> restoring the model from persistence again.
>
> BUG=1152924
>
> Change-Id: Iffe4586bcb87f47d687ceeff0fc76e60a443c0d5
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2568355
> Commit-Queue: Toni Baržić <tbarzic@chromium.org>
> Reviewed-by: David Black <dmblack@google.com>
> Cr-Commit-Position: refs/heads/master@{#833515}

Bug: 1152924
Change-Id: I4ef1fa83d684260e4ebf66fe4c01e9627e5c46af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2573893Reviewed-by: default avatarDavid Black <dmblack@google.com>
Commit-Queue: Toni Baržić <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833787}
parent f7c45b69
...@@ -20,9 +20,11 @@ source_set("browser_tests") { ...@@ -20,9 +20,11 @@ source_set("browser_tests") {
"//base/test:test_support", "//base/test:test_support",
"//chrome/browser", "//chrome/browser",
"//chrome/browser/chromeos", "//chrome/browser/chromeos",
"//chrome/browser/chromeos:test_support",
"//chrome/browser/extensions", "//chrome/browser/extensions",
"//chrome/browser/ui", "//chrome/browser/ui",
"//chrome/test:test_support_ui", "//chrome/test:test_support_ui",
"//chromeos/dbus/power:power",
"//content/test:test_support", "//content/test:test_support",
"//ui/aura", "//ui/aura",
"//ui/base", "//ui/base",
......
...@@ -41,10 +41,6 @@ void HoldingSpaceDownloadsDelegate::Init() { ...@@ -41,10 +41,6 @@ void HoldingSpaceDownloadsDelegate::Init() {
: content::BrowserContext::GetDownloadManager(profile())); : content::BrowserContext::GetDownloadManager(profile()));
} }
void HoldingSpaceDownloadsDelegate::Shutdown() {
RemoveObservers();
}
void HoldingSpaceDownloadsDelegate::OnPersistenceRestored() { void HoldingSpaceDownloadsDelegate::OnPersistenceRestored() {
content::DownloadManager* download_manager = content::DownloadManager* download_manager =
download_manager_for_testing download_manager_for_testing
......
...@@ -47,7 +47,6 @@ class HoldingSpaceDownloadsDelegate : public HoldingSpaceKeyedServiceDelegate, ...@@ -47,7 +47,6 @@ class HoldingSpaceDownloadsDelegate : public HoldingSpaceKeyedServiceDelegate,
private: private:
// HoldingSpaceKeyedServiceDelegate: // HoldingSpaceKeyedServiceDelegate:
void Init() override; void Init() override;
void Shutdown() override;
void OnPersistenceRestored() override; void OnPersistenceRestored() override;
// content::DownloadManager::Observer: // content::DownloadManager::Observer:
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "ash/public/cpp/holding_space/holding_space_model.h" #include "ash/public/cpp/holding_space/holding_space_model.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_path_watcher.h" #include "base/files/file_path_watcher.h"
#include "base/files/file_util.h"
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
#include "base/task/thread_pool.h" #include "base/task/thread_pool.h"
...@@ -32,15 +33,26 @@ class HoldingSpaceFileSystemDelegate::FileSystemWatcher { ...@@ -32,15 +33,26 @@ class HoldingSpaceFileSystemDelegate::FileSystemWatcher {
FileSystemWatcher& operator=(const FileSystemWatcher&) = delete; FileSystemWatcher& operator=(const FileSystemWatcher&) = delete;
~FileSystemWatcher() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } ~FileSystemWatcher() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
void AddWatch(const base::FilePath& file_path) { void AddWatchForParent(const base::FilePath& file_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::Contains(watchers_, file_path))
return; // Observe the file path parent directory for changes - this reduces the
watchers_[file_path] = std::make_unique<base::FilePathWatcher>(); // number of inotify requests, and works well enough for detecting file
watchers_[file_path]->Watch( // deletion.
file_path, base::FilePathWatcher::Type::kNonRecursive, const base::FilePath path_to_watch = file_path.DirName();
base::Bind(&FileSystemWatcher::OnFilePathChanged,
weak_factory_.GetWeakPtr())); if (!base::Contains(watchers_, path_to_watch)) {
watchers_[path_to_watch] = std::make_unique<base::FilePathWatcher>();
watchers_[path_to_watch]->Watch(
path_to_watch, base::FilePathWatcher::Type::kNonRecursive,
base::Bind(&FileSystemWatcher::OnFilePathChanged,
weak_factory_.GetWeakPtr()));
}
// If the target path got deleted while request to add a watcher was in
// flight, notify observers of path change immediately.
if (!base::PathExists(file_path))
OnFilePathChanged(path_to_watch, /*error=*/false);
} }
void RemoveWatch(const base::FilePath& file_path) { void RemoveWatch(const base::FilePath& file_path) {
...@@ -102,10 +114,6 @@ void HoldingSpaceFileSystemDelegate::Init() { ...@@ -102,10 +114,6 @@ void HoldingSpaceFileSystemDelegate::Init() {
base::Unretained(this))); base::Unretained(this)));
} }
void HoldingSpaceFileSystemDelegate::Shutdown() {
volume_manager_observer_.RemoveAll();
}
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemAdded( void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemAdded(
const HoldingSpaceItem* item) { const HoldingSpaceItem* item) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
...@@ -113,7 +121,7 @@ void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemAdded( ...@@ -113,7 +121,7 @@ void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemAdded(
if (item->IsFinalized()) { if (item->IsFinalized()) {
// Watch the directory containing `items`'s backing file. If the directory // Watch the directory containing `items`'s backing file. If the directory
// is already being watched, this will no-op. // is already being watched, this will no-op.
AddWatch(item->file_path().DirName()); AddWatchForParent(item->file_path());
return; return;
} }
...@@ -162,7 +170,7 @@ void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemRemoved( ...@@ -162,7 +170,7 @@ void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemRemoved(
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemFinalized( void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemFinalized(
const HoldingSpaceItem* item) { const HoldingSpaceItem* item) {
AddWatch(item->file_path().DirName()); AddWatchForParent(item->file_path());
} }
void HoldingSpaceFileSystemDelegate::OnVolumeMounted( void HoldingSpaceFileSystemDelegate::OnVolumeMounted(
...@@ -188,11 +196,17 @@ void HoldingSpaceFileSystemDelegate::OnVolumeMounted( ...@@ -188,11 +196,17 @@ void HoldingSpaceFileSystemDelegate::OnVolumeMounted(
void HoldingSpaceFileSystemDelegate::OnVolumeUnmounted( void HoldingSpaceFileSystemDelegate::OnVolumeUnmounted(
chromeos::MountError error_code, chromeos::MountError error_code,
const file_manager::Volume& volume) { const file_manager::Volume& volume) {
model()->RemoveIf(base::BindRepeating( // Schedule task to remove items under the unmounted file path from the model.
[](const base::FilePath& volume_path, const HoldingSpaceItem* item) { // During suspend, some volumes get unmounted - for example, drive FS. The
return volume_path.IsParent(item->file_path()); // file system delegate gets shutdown to avoid removing items from unmounted
}, // volumes, but depending on the order in which observers are added to power
volume.mount_path())); // manager dbus client, the file system delegate may get shutdown after
// unmounting a volume. To avoid observer ordering issues, schedule
// asynchronous task to remove unmounted items from the model.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&HoldingSpaceFileSystemDelegate::RemoveItemsParentedByPath,
weak_factory_.GetWeakPtr(), volume.mount_path()));
} }
void HoldingSpaceFileSystemDelegate::OnFilePathChanged( void HoldingSpaceFileSystemDelegate::OnFilePathChanged(
...@@ -269,10 +283,11 @@ void HoldingSpaceFileSystemDelegate::OnFilePathValidityChecksComplete( ...@@ -269,10 +283,11 @@ void HoldingSpaceFileSystemDelegate::OnFilePathValidityChecksComplete(
} }
} }
void HoldingSpaceFileSystemDelegate::AddWatch(const base::FilePath& file_path) { void HoldingSpaceFileSystemDelegate::AddWatchForParent(
const base::FilePath& file_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
file_system_watcher_runner_->PostTask( file_system_watcher_runner_->PostTask(
FROM_HERE, base::BindOnce(&FileSystemWatcher::AddWatch, FROM_HERE, base::BindOnce(&FileSystemWatcher::AddWatchForParent,
file_system_watcher_->GetWeakPtr(), file_path)); file_system_watcher_->GetWeakPtr(), file_path));
} }
...@@ -284,6 +299,15 @@ void HoldingSpaceFileSystemDelegate::RemoveWatch( ...@@ -284,6 +299,15 @@ void HoldingSpaceFileSystemDelegate::RemoveWatch(
file_system_watcher_->GetWeakPtr(), file_path)); file_system_watcher_->GetWeakPtr(), file_path));
} }
void HoldingSpaceFileSystemDelegate::RemoveItemsParentedByPath(
const base::FilePath& parent_path) {
model()->RemoveIf(base::BindRepeating(
[](const base::FilePath& parent_path, const HoldingSpaceItem* item) {
return parent_path.IsParent(item->file_path());
},
parent_path));
}
void HoldingSpaceFileSystemDelegate::ClearNonFinalizedItems() { void HoldingSpaceFileSystemDelegate::ClearNonFinalizedItems() {
model()->RemoveIf(base::BindRepeating( model()->RemoveIf(base::BindRepeating(
[](const HoldingSpaceItem* item) { return !item->IsFinalized(); })); [](const HoldingSpaceItem* item) { return !item->IsFinalized(); }));
......
...@@ -42,7 +42,6 @@ class HoldingSpaceFileSystemDelegate : public HoldingSpaceKeyedServiceDelegate, ...@@ -42,7 +42,6 @@ class HoldingSpaceFileSystemDelegate : public HoldingSpaceKeyedServiceDelegate,
// HoldingSpaceKeyedServiceDelegate: // HoldingSpaceKeyedServiceDelegate:
void Init() override; void Init() override;
void Shutdown() override;
void OnHoldingSpaceItemAdded(const HoldingSpaceItem* item) override; void OnHoldingSpaceItemAdded(const HoldingSpaceItem* item) override;
void OnHoldingSpaceItemRemoved(const HoldingSpaceItem* item) override; void OnHoldingSpaceItemRemoved(const HoldingSpaceItem* item) override;
void OnHoldingSpaceItemFinalized(const HoldingSpaceItem* item) override; void OnHoldingSpaceItemFinalized(const HoldingSpaceItem* item) override;
...@@ -74,9 +73,15 @@ class HoldingSpaceFileSystemDelegate : public HoldingSpaceKeyedServiceDelegate, ...@@ -74,9 +73,15 @@ class HoldingSpaceFileSystemDelegate : public HoldingSpaceKeyedServiceDelegate,
std::vector<base::FilePath> invalid_paths); std::vector<base::FilePath> invalid_paths);
// Adds/removes a watch for the specified `file_path`. // Adds/removes a watch for the specified `file_path`.
void AddWatch(const base::FilePath& file_path); // Note that `AddWatchForParent` will add a watch for the `file_path`'s parent
// directory.
void AddWatchForParent(const base::FilePath& file_path);
void RemoveWatch(const base::FilePath& file_path); void RemoveWatch(const base::FilePath& file_path);
// Removes items that are (transitively) parented by `parent_path` from the
// holding space model.
void RemoveItemsParentedByPath(const base::FilePath& parent_path);
// Clears all non-finalized items from holding space model - runs with a delay // Clears all non-finalized items from holding space model - runs with a delay
// after profile initialization to clean up items from volumes that have not // after profile initialization to clean up items from volumes that have not
// been mounted during startup. // been mounted during startup.
......
...@@ -96,7 +96,7 @@ HoldingSpaceKeyedService::HoldingSpaceKeyedService(Profile* profile, ...@@ -96,7 +96,7 @@ HoldingSpaceKeyedService::HoldingSpaceKeyedService(Profile* profile,
} }
// Otherwise we need to wait for the profile to be added. // Otherwise we need to wait for the profile to be added.
profile_manager_observer_.Add(profile_manager); profile_manager_observer_.Observe(profile_manager);
} }
HoldingSpaceKeyedService::~HoldingSpaceKeyedService() { HoldingSpaceKeyedService::~HoldingSpaceKeyedService() {
...@@ -269,18 +269,47 @@ void HoldingSpaceKeyedService::AddItem(std::unique_ptr<HoldingSpaceItem> item) { ...@@ -269,18 +269,47 @@ void HoldingSpaceKeyedService::AddItem(std::unique_ptr<HoldingSpaceItem> item) {
} }
void HoldingSpaceKeyedService::Shutdown() { void HoldingSpaceKeyedService::Shutdown() {
for (auto& delegate : delegates_) ShutdownDelegates();
delegate->Shutdown();
} }
void HoldingSpaceKeyedService::OnProfileAdded(Profile* profile) { void HoldingSpaceKeyedService::OnProfileAdded(Profile* profile) {
if (profile == profile_) { if (profile == profile_) {
profile_manager_observer_.Remove(GetProfileManager()); profile_manager_observer_.RemoveObservation();
OnProfileReady(); OnProfileReady();
} }
} }
void HoldingSpaceKeyedService::OnProfileReady() { void HoldingSpaceKeyedService::OnProfileReady() {
// Observe suspend status - the delegates will be shutdown during suspend.
if (chromeos::PowerManagerClient::Get())
power_manager_observer_.Observe(chromeos::PowerManagerClient::Get());
InitializeDelegates();
HoldingSpaceController::Get()->RegisterClientAndModelForUser(
account_id_, &holding_space_client_, &holding_space_model_);
}
void HoldingSpaceKeyedService::SuspendImminent(
power_manager::SuspendImminent::Reason reason) {
// Shutdown all delegates and clear the model when device suspends - some
// volumes may get unmounted during suspend, and may thus incorrectly get
// detected as deleted when device suspends - shutting down delegates during
// suspend avoids this issue, as it also disables file removal detection.
ShutdownDelegates();
// Clear the model as it will get restored from persistence when
// delegates are re-initialized after suspend.
holding_space_model_.RemoveAll();
}
void HoldingSpaceKeyedService::SuspendDone(base::TimeDelta sleep_duration) {
InitializeDelegates();
}
void HoldingSpaceKeyedService::InitializeDelegates() {
DCHECK(delegates_.empty());
// The `HoldingSpaceDownloadsDelegate` monitors the status of downloads. // The `HoldingSpaceDownloadsDelegate` monitors the status of downloads.
delegates_.push_back(std::make_unique<HoldingSpaceDownloadsDelegate>( delegates_.push_back(std::make_unique<HoldingSpaceDownloadsDelegate>(
profile_, &holding_space_model_, profile_, &holding_space_model_,
...@@ -309,12 +338,13 @@ void HoldingSpaceKeyedService::OnProfileReady() { ...@@ -309,12 +338,13 @@ void HoldingSpaceKeyedService::OnProfileReady() {
delegate->Init(); delegate->Init();
} }
void HoldingSpaceKeyedService::ShutdownDelegates() {
delegates_.clear();
}
void HoldingSpaceKeyedService::OnPersistenceRestored() { void HoldingSpaceKeyedService::OnPersistenceRestored() {
for (auto& delegate : delegates_) for (auto& delegate : delegates_)
delegate->NotifyPersistenceRestored(); delegate->NotifyPersistenceRestored();
HoldingSpaceController::Get()->RegisterClientAndModelForUser(
account_id_, &holding_space_client_, &holding_space_model_);
} }
} // namespace ash } // namespace ash
...@@ -9,13 +9,14 @@ ...@@ -9,13 +9,14 @@
#include <vector> #include <vector>
#include "ash/public/cpp/holding_space/holding_space_model.h" #include "ash/public/cpp/holding_space/holding_space_model.h"
#include "base/scoped_observer.h" #include "base/scoped_observation.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_manager_observer.h" #include "chrome/browser/profiles/profile_manager_observer.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_client_impl.h" #include "chrome/browser/ui/ash/holding_space/holding_space_client_impl.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h" #include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader.h" #include "chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/account_id/account_id.h" #include "components/account_id/account_id.h"
#include "components/keyed_service/core/keyed_service.h" #include "components/keyed_service/core/keyed_service.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -40,7 +41,8 @@ namespace ash { ...@@ -40,7 +41,8 @@ namespace ash {
// * Manages the temporary holding space per-profile data model. // * Manages the temporary holding space per-profile data model.
// * Serves as an entry point to add holding space items from Chrome. // * Serves as an entry point to add holding space items from Chrome.
class HoldingSpaceKeyedService : public KeyedService, class HoldingSpaceKeyedService : public KeyedService,
public ProfileManagerObserver { public ProfileManagerObserver,
public chromeos::PowerManagerClient::Observer {
public: public:
HoldingSpaceKeyedService(Profile* profile, const AccountId& account_id); HoldingSpaceKeyedService(Profile* profile, const AccountId& account_id);
HoldingSpaceKeyedService(const HoldingSpaceKeyedService& other) = delete; HoldingSpaceKeyedService(const HoldingSpaceKeyedService& other) = delete;
...@@ -102,9 +104,22 @@ class HoldingSpaceKeyedService : public KeyedService, ...@@ -102,9 +104,22 @@ class HoldingSpaceKeyedService : public KeyedService,
// ProfileManagerObserver: // ProfileManagerObserver:
void OnProfileAdded(Profile* profile) override; void OnProfileAdded(Profile* profile) override;
// PowerManagerClient::Observer
void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
void SuspendDone(base::TimeDelta sleep_duration) override;
// Invoked when the associated profile is ready. // Invoked when the associated profile is ready.
void OnProfileReady(); void OnProfileReady();
// Creates and initializes holding space delegates. Called when the associated
// profile finishes initialization, or when device suspend ends (the delegates
// are shutdown during suspend).
void InitializeDelegates();
// Shuts down and destroys existing holding space delegates. Called on
// profile shutdown, or when device suspend starts.
void ShutdownDelegates();
// Invoked when holding space persistence has been restored. // Invoked when holding space persistence has been restored.
void OnPersistenceRestored(); void OnPersistenceRestored();
...@@ -121,9 +136,13 @@ class HoldingSpaceKeyedService : public KeyedService, ...@@ -121,9 +136,13 @@ class HoldingSpaceKeyedService : public KeyedService,
// service. They operate autonomously of one another. // service. They operate autonomously of one another.
std::vector<std::unique_ptr<HoldingSpaceKeyedServiceDelegate>> delegates_; std::vector<std::unique_ptr<HoldingSpaceKeyedServiceDelegate>> delegates_;
ScopedObserver<ProfileManager, ProfileManagerObserver> base::ScopedObservation<ProfileManager, ProfileManagerObserver>
profile_manager_observer_{this}; profile_manager_observer_{this};
base::ScopedObservation<chromeos::PowerManagerClient,
chromeos::PowerManagerClient::Observer>
power_manager_observer_{this};
base::WeakPtrFactory<HoldingSpaceKeyedService> weak_factory_{this}; base::WeakPtrFactory<HoldingSpaceKeyedService> weak_factory_{this};
}; };
......
...@@ -12,16 +12,26 @@ ...@@ -12,16 +12,26 @@
#include "ash/public/cpp/holding_space/holding_space_model_observer.h" #include "ash/public/cpp/holding_space/holding_space_model_observer.h"
#include "base/callback_helpers.h" #include "base/callback_helpers.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/bind.h" #include "base/test/bind.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/scoped_path_override.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/drive/drivefs_test_support.h"
#include "chrome/browser/chromeos/file_manager/path_util.h" #include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/extensions/component_loader.h" #include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_util.h" #include "chrome/browser/ui/ash/holding_space/holding_space_util.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "content/public/test/browser_test.h" #include "content/public/test/browser_test.h"
#include "storage/browser/file_system/external_mount_points.h" #include "storage/browser/file_system/external_mount_points.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
...@@ -31,6 +41,11 @@ ...@@ -31,6 +41,11 @@
namespace ash { namespace ash {
namespace { namespace {
// Types of file systems backing holding space items tested by
// HoldingSpaceKeyedServiceBrowserTest. The tests are parameterized by this
// enum.
enum class FileSystemType { kDownloads, kDriveFs };
// Mocks ----------------------------------------------------------------------- // Mocks -----------------------------------------------------------------------
// Mock observer which can be used to set expectations about model behavior. // Mock observer which can be used to set expectations about model behavior.
...@@ -52,14 +67,6 @@ class MockHoldingSpaceModelObserver : public HoldingSpaceModelObserver { ...@@ -52,14 +67,6 @@ class MockHoldingSpaceModelObserver : public HoldingSpaceModelObserver {
// Helpers --------------------------------------------------------------------- // Helpers ---------------------------------------------------------------------
// Posts a task and waits for it to run in order to flush the message loop.
void FlushMessageLoop() {
base::RunLoop run_loop;
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
}
// Returns the path of the downloads mount point for the given `profile`. // Returns the path of the downloads mount point for the given `profile`.
base::FilePath GetDownloadsPath(Profile* profile) { base::FilePath GetDownloadsPath(Profile* profile) {
base::FilePath result; base::FilePath result;
...@@ -70,10 +77,12 @@ base::FilePath GetDownloadsPath(Profile* profile) { ...@@ -70,10 +77,12 @@ base::FilePath GetDownloadsPath(Profile* profile) {
} }
// Creates a txt file at the path of the downloads mount point for `profile`. // Creates a txt file at the path of the downloads mount point for `profile`.
base::FilePath CreateTextFile(Profile* profile) { base::FilePath CreateTextFile(
const std::string relative_path = base::StringPrintf( const base::FilePath& root_path,
"%s.txt", base::UnguessableToken::Create().ToString().c_str()); const base::Optional<std::string>& relative_path) {
const base::FilePath path = GetDownloadsPath(profile).Append(relative_path); const base::FilePath path =
root_path.Append(relative_path.value_or(base::StringPrintf(
"%s.txt", base::UnguessableToken::Create().ToString().c_str())));
base::ScopedAllowBlockingForTesting allow_blocking; base::ScopedAllowBlockingForTesting allow_blocking;
if (!base::CreateDirectory(path.DirName())) if (!base::CreateDirectory(path.DirName()))
...@@ -84,72 +93,124 @@ base::FilePath CreateTextFile(Profile* profile) { ...@@ -84,72 +93,124 @@ base::FilePath CreateTextFile(Profile* profile) {
return path; return path;
} }
// Adds a holding space item backed by a txt file at the path of the downloads // Waits for a holding space item with the provided id to be added to the
// mount point for `profile`. A pointer to the added item is returned. // holding space model.
const HoldingSpaceItem* AddHoldingSpaceItem(Profile* profile) { // Returns immediately if the item already exists.
EXPECT_TRUE(ash::HoldingSpaceController::Get()); void WaitForItemAddition(const std::string& item_id) {
auto* holding_space_model = ash::HoldingSpaceController::Get()->model(); auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
EXPECT_TRUE(holding_space_model); if (holding_space_model->GetItem(item_id))
return;
const HoldingSpaceItem* result = nullptr; testing::NiceMock<MockHoldingSpaceModelObserver> mock;
base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
testing::StrictMock<MockHoldingSpaceModelObserver> mock; observer{&mock};
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver> observer{&mock}; observer.Observe(holding_space_model);
observer.Add(holding_space_model);
base::RunLoop run_loop; base::RunLoop run_loop;
EXPECT_CALL(mock, OnHoldingSpaceItemAdded) ON_CALL(mock, OnHoldingSpaceItemAdded)
.WillOnce([&](const HoldingSpaceItem* item) { .WillByDefault([&](const HoldingSpaceItem* item) {
result = item; if (item->id() == item_id)
run_loop.Quit();
});
run_loop.Run();
}
// Explicitly flush the message loop after a holding space `item` is // Waits for a holding space item with the provided id to be removed from the
// added to give file system watchers a chance to register. // holding space model.
FlushMessageLoop(); // Returns immediately if the model does not contain such item.
run_loop.Quit(); void WaitForItemRemoval(const std::string& item_id) {
auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
if (!holding_space_model->GetItem(item_id))
return;
testing::NiceMock<MockHoldingSpaceModelObserver> mock;
base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
observer{&mock};
observer.Observe(holding_space_model);
base::RunLoop run_loop;
ON_CALL(mock, OnHoldingSpaceItemRemoved)
.WillByDefault([&](const HoldingSpaceItem* item) {
if (item->id() == item_id)
run_loop.Quit();
}); });
run_loop.Run();
}
base::FilePath item_path = CreateTextFile(profile); // Waits for a holding space item with the provided id to be added to the
holding_space_model->AddItem(HoldingSpaceItem::CreateFileBackedItem( // holding space model and finalized.
HoldingSpaceItem::Type::kDownload, item_path, // Returns immediately if the item already exists and is finalized.
holding_space_util::ResolveFileSystemUrl(profile, item_path), void WaitForItemFinalization(const std::string& item_id) {
std::make_unique<HoldingSpaceImage>( auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
/*placeholder=*/gfx::ImageSkia(), if (!holding_space_model->GetItem(item_id))
/*async_bitmap_resolver=*/base::DoNothing()))); WaitForItemAddition(item_id);
const HoldingSpaceItem* item = holding_space_model->GetItem(item_id);
if (item->IsFinalized())
return;
testing::NiceMock<MockHoldingSpaceModelObserver> mock;
base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
observer{&mock};
observer.Observe(holding_space_model);
base::RunLoop run_loop;
ON_CALL(mock, OnHoldingSpaceItemFinalized)
.WillByDefault([&](const HoldingSpaceItem* item) {
if (item->id() == item_id)
run_loop.Quit();
});
ON_CALL(mock, OnHoldingSpaceItemRemoved)
.WillByDefault([&](const HoldingSpaceItem* item) {
if (item->id() != item_id)
return;
ADD_FAILURE() << "Item unexpectedly removed " << item_id;
run_loop.Quit();
});
run_loop.Run(); run_loop.Run();
return result;
} }
// Removes a `holding_space_item` by running the specified `closure`. // Adds a holding space item backed by a txt file at `item_path`.
void RemoveHoldingSpaceItemViaClosure( // Returns a pointer to the added item.
const HoldingSpaceItem* holding_space_item, const HoldingSpaceItem* AddHoldingSpaceItem(Profile* profile,
base::OnceClosure closure) { const base::FilePath& item_path) {
EXPECT_TRUE(ash::HoldingSpaceController::Get()); EXPECT_TRUE(ash::HoldingSpaceController::Get());
auto* holding_space_model = ash::HoldingSpaceController::Get()->model(); auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
EXPECT_TRUE(holding_space_model); EXPECT_TRUE(holding_space_model);
testing::StrictMock<MockHoldingSpaceModelObserver> mock; std::unique_ptr<HoldingSpaceItem> item =
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver> observer{&mock}; HoldingSpaceItem::CreateFileBackedItem(
observer.Add(holding_space_model); HoldingSpaceItem::Type::kDownload, item_path,
holding_space_util::ResolveFileSystemUrl(profile, item_path),
std::make_unique<HoldingSpaceImage>(
/*placeholder=*/gfx::ImageSkia(),
/*async_bitmap_resolver=*/base::DoNothing()));
base::RunLoop run_loop; const HoldingSpaceItem* item_ptr = item.get();
EXPECT_CALL(mock, OnHoldingSpaceItemRemoved) holding_space_model->AddItem(std::move(item));
.WillOnce([&](const HoldingSpaceItem* item) {
EXPECT_EQ(holding_space_item, item);
run_loop.Quit();
});
return item_ptr;
}
// Removes a `holding_space_item` by running the specified `closure`.
void RemoveHoldingSpaceItemViaClosure(
const HoldingSpaceItem* holding_space_item,
base::OnceClosure closure) {
EXPECT_TRUE(ash::HoldingSpaceController::Get());
const std::string item_id = holding_space_item->id();
std::move(closure).Run(); std::move(closure).Run();
run_loop.Run(); WaitForItemRemoval(item_id);
} }
} // namespace } // namespace
// HoldingSpaceKeyedServiceBrowserTest ----------------------------------------- // HoldingSpaceKeyedServiceBrowserTest -----------------------------------------
class HoldingSpaceKeyedServiceBrowserTest : public InProcessBrowserTest { class HoldingSpaceKeyedServiceBrowserTest
: public InProcessBrowserTest,
public ::testing::WithParamInterface<FileSystemType> {
public: public:
HoldingSpaceKeyedServiceBrowserTest() { HoldingSpaceKeyedServiceBrowserTest() {
scoped_feature_list_.InitAndEnableFeature( scoped_feature_list_.InitAndEnableFeature(
...@@ -157,43 +218,216 @@ class HoldingSpaceKeyedServiceBrowserTest : public InProcessBrowserTest { ...@@ -157,43 +218,216 @@ class HoldingSpaceKeyedServiceBrowserTest : public InProcessBrowserTest {
} }
// InProcessBrowserTest: // InProcessBrowserTest:
bool SetUpUserDataDirectory() override {
base::FilePath user_data_dir;
if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
return false;
// Mount test volumes under user data dir to ensure it gets persisted after
// PRE test runs.
test_mount_point_ = user_data_dir.Append("test_mount").Append("test-user");
return GetParam() == FileSystemType::kDriveFs
? drive::SetUpUserDataDirectoryForDriveFsTest()
: InProcessBrowserTest::SetUpUserDataDirectory();
}
void SetUpInProcessBrowserTestFixture() override { void SetUpInProcessBrowserTestFixture() override {
InProcessBrowserTest::SetUpInProcessBrowserTestFixture(); InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
extensions::ComponentLoader::EnableBackgroundExtensionsForTesting(); extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
// File system type specific setup.
switch (GetParam()) {
case FileSystemType::kDownloads:
// Override the default downloads path to point to the test mount point
// within user data dir.
downloads_override_ = std::make_unique<base::ScopedPathOverride>(
chrome::DIR_DEFAULT_DOWNLOADS, test_mount_point_,
/*is_absolute*/ true,
/*create*/ false);
break;
case FileSystemType::kDriveFs:
// Set up drive integration service for test.
ASSERT_TRUE(test_cache_root_.CreateUniqueTempDir());
create_drive_integration_service_ = base::Bind(
&HoldingSpaceKeyedServiceBrowserTest::CreateDriveIntegrationService,
base::Unretained(this));
service_factory_for_test_ = std::make_unique<
drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>(
&create_drive_integration_service_);
break;
}
EnsurePredefinedTestFiles();
}
base::FilePath GetTestMountPoint() { return test_mount_point_; }
base::FilePath GetPredefinedTestFile(size_t index) const {
DCHECK_LT(index, predefined_test_files_.size());
return predefined_test_files_[index];
}
void EnsurePredefinedTestFiles() {
if (!predefined_test_files_.empty())
return;
predefined_test_files_.push_back(
CreateTextFile(GetTestMountPoint(), "root/test_file.txt"));
}
drive::DriveIntegrationService* CreateDriveIntegrationService(
Profile* profile) {
// Ignore signin and lock screen apps profile.
if (profile->GetPath() == chromeos::ProfileHelper::GetSigninProfileDir() ||
profile->GetPath() ==
chromeos::ProfileHelper::GetLockScreenAppProfilePath()) {
return nullptr;
}
fake_drivefs_helper_ =
std::make_unique<drive::FakeDriveFsHelper>(profile, test_mount_point_);
integration_service_ = new drive::DriveIntegrationService(
profile, "", test_cache_root_.GetPath(),
fake_drivefs_helper_->CreateFakeDriveFsListenerFactory());
return integration_service_;
}
void WaitForVolumeUnmountIfNeeded() {
// Drive fs gets unmounted on suspend, and the fake cros disks client
// deletes the mount point on unmount event - wait for the drive mount point
// to get deleted from file system.
if (GetParam() != FileSystemType::kDriveFs)
return;
// Clear the list of predefined test files, as they are getting deleted with
// the mount point dir.
predefined_test_files_.clear();
const base::FilePath mount_path = GetTestMountPoint();
base::ScopedAllowBlockingForTesting allow_blocking;
if (!base::PathExists(mount_path))
return;
base::RunLoop waiter_loop;
base::FilePathWatcher watcher;
watcher.Watch(mount_path, base::FilePathWatcher::Type::kNonRecursive,
base::BindRepeating(
[](const base::RepeatingClosure& callback,
const base::FilePath& path, bool error) {
if (!base::PathExists(path))
callback.Run();
},
waiter_loop.QuitClosure()));
waiter_loop.Run();
} }
private: private:
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
// List of files paths that are created by default by the test suite.
std::vector<base::FilePath> predefined_test_files_;
// The path under which test volume is mounted.
base::FilePath test_mount_point_;
// Used to override downloads mount point for downloads tests.
std::unique_ptr<base::ScopedPathOverride> downloads_override_;
// Used to set up drive fs for for drive tests.
base::ScopedTempDir test_cache_root_;
std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
drive::DriveIntegrationService* integration_service_ = nullptr;
drive::DriveIntegrationServiceFactory::FactoryCallback
create_drive_integration_service_;
std::unique_ptr<drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>
service_factory_for_test_;
}; };
INSTANTIATE_TEST_SUITE_P(FileSystem,
HoldingSpaceKeyedServiceBrowserTest,
::testing::Values(FileSystemType::kDownloads,
FileSystemType::kDriveFs));
// Tests ----------------------------------------------------------------------- // Tests -----------------------------------------------------------------------
// Verifies that holding space items are removed when their backing files // Verifies that holding space items are removed when their backing files
// "disappear". Note that a "disappearance" could be due to file move or delete. // "disappear". Note that a "disappearance" could be due to file move or delete.
IN_PROC_BROWSER_TEST_F(HoldingSpaceKeyedServiceBrowserTest, IN_PROC_BROWSER_TEST_P(HoldingSpaceKeyedServiceBrowserTest,
RemovesItemsWhenBackingFileDisappears) { RemovesItemsWhenBackingFileDisappears) {
{ // Verify that items are removed when their backing files are deleted.
// Verify that items are removed when their backing files are deleted. const auto* holding_space_item_to_delete = AddHoldingSpaceItem(
const auto* holding_space_item = AddHoldingSpaceItem(browser()->profile()); browser()->profile(), CreateTextFile(GetTestMountPoint(),
RemoveHoldingSpaceItemViaClosure( /*relative_path=*/base::nullopt));
holding_space_item, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking; // Verify that items are removed when their backing files are moved.
EXPECT_TRUE(base::DeleteFile(holding_space_item->file_path())); const auto* holding_space_item_to_move = AddHoldingSpaceItem(
})); browser()->profile(), CreateTextFile(GetTestMountPoint(),
} /*relative_path=*/base::nullopt));
RemoveHoldingSpaceItemViaClosure(
holding_space_item_to_delete, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(
base::DeleteFile(holding_space_item_to_delete->file_path()));
}));
RemoveHoldingSpaceItemViaClosure(
holding_space_item_to_move, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(
base::Move(holding_space_item_to_move->file_path(),
GetTestMountPoint().Append(
base::UnguessableToken::Create().ToString())));
}));
}
{ IN_PROC_BROWSER_TEST_P(HoldingSpaceKeyedServiceBrowserTest,
// Verify that items are removed when their backing files are moved. ItemsNotRemovedDuringSuspend) {
const auto* holding_space_item = AddHoldingSpaceItem(browser()->profile()); const auto* holding_space_item =
RemoveHoldingSpaceItemViaClosure( AddHoldingSpaceItem(browser()->profile(), GetPredefinedTestFile(0));
holding_space_item, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking; auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
EXPECT_TRUE(base::Move( EXPECT_TRUE(holding_space_model);
holding_space_item->file_path(), ASSERT_TRUE(holding_space_model->GetItem(holding_space_item->id()));
GetDownloadsPath(browser()->profile()) const std::string item_id = holding_space_item->id();
.Append(base::UnguessableToken::Create().ToString())));
})); chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
} power_manager::SuspendImminent::IDLE);
base::RunLoop().RunUntilIdle();
// Holding space model gets cleared on suspend.
EXPECT_TRUE(holding_space_model->items().empty());
// Wait for test volume unmount to finish, if necessary for the test file
// system - for example, the drive fs will be unmounted, and fake cros disks
// client will delete the backing directory from files system.
WaitForVolumeUnmountIfNeeded();
EnsurePredefinedTestFiles();
// Verify that holding space model gets restored on resume.
chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
WaitForItemFinalization(item_id);
EXPECT_TRUE(holding_space_model->GetItem(item_id));
}
// Test that creates a holding space item during PRE_ part, and verifies that
// the item gets restored after restart.
IN_PROC_BROWSER_TEST_P(HoldingSpaceKeyedServiceBrowserTest,
PRE_RestoreItemsOnRestart) {
const auto* holding_space_item =
AddHoldingSpaceItem(browser()->profile(), GetPredefinedTestFile(0));
auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
ASSERT_TRUE(holding_space_model);
EXPECT_TRUE(holding_space_model->GetItem(holding_space_item->id()));
}
IN_PROC_BROWSER_TEST_P(HoldingSpaceKeyedServiceBrowserTest,
RestoreItemsOnRestart) {
WaitForItemFinalization(HoldingSpaceItem::GetFileBackedItemId(
HoldingSpaceItem::Type::kDownload, GetPredefinedTestFile(0)));
} }
} // namespace ash } // namespace ash
...@@ -19,8 +19,6 @@ ProfileManager* GetProfileManager() { ...@@ -19,8 +19,6 @@ ProfileManager* GetProfileManager() {
HoldingSpaceKeyedServiceDelegate::~HoldingSpaceKeyedServiceDelegate() = default; HoldingSpaceKeyedServiceDelegate::~HoldingSpaceKeyedServiceDelegate() = default;
void HoldingSpaceKeyedServiceDelegate::Shutdown() {}
void HoldingSpaceKeyedServiceDelegate::NotifyPersistenceRestored() { void HoldingSpaceKeyedServiceDelegate::NotifyPersistenceRestored() {
DCHECK(is_restoring_persistence_); DCHECK(is_restoring_persistence_);
is_restoring_persistence_ = false; is_restoring_persistence_ = false;
......
...@@ -19,16 +19,12 @@ class HoldingSpaceKeyedServiceDelegate : public HoldingSpaceModelObserver { ...@@ -19,16 +19,12 @@ class HoldingSpaceKeyedServiceDelegate : public HoldingSpaceModelObserver {
public: public:
~HoldingSpaceKeyedServiceDelegate() override; ~HoldingSpaceKeyedServiceDelegate() override;
// Invoked by `HoldingSpaceKeyedService` to initialize the delegate // Invoked by `HoldingSpaceKeyedService` to initialize the delegate.
// immediately after its construction. Delegates accepting callbacks from // Called immediately after the delegate's construction. Delegates accepting
// the service should *not* invoke callbacks during construction but are free // callbacks from the service should *not* invoke callbacks during
// to do so during or anytime after initialization. // construction but are free to do so during or anytime after initialization.
virtual void Init() = 0; virtual void Init() = 0;
// Invoked by `HoldingSpaceKeyedService` when the service is shutting down.
// Delegates should perform any necessary clean up.
virtual void Shutdown();
// Invoked by `HoldingSpaceKeyedService` to notify delegates when holding // Invoked by `HoldingSpaceKeyedService` to notify delegates when holding
// space persistence has been restored. // space persistence has been restored.
void NotifyPersistenceRestored(); void NotifyPersistenceRestored();
......
...@@ -1085,6 +1085,7 @@ TEST_F(HoldingSpaceKeyedServiceTest, RemoveItemsFromUnmountedVolumes) { ...@@ -1085,6 +1085,7 @@ TEST_F(HoldingSpaceKeyedServiceTest, RemoveItemsFromUnmountedVolumes) {
EXPECT_EQ(3u, holding_space_model->items().size()); EXPECT_EQ(3u, holding_space_model->items().size());
test_mount_1.reset(); test_mount_1.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, GetProfile() EXPECT_EQ(1u, GetProfile()
->GetPrefs() ->GetPrefs()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment