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

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: default avatarDavid Black <dmblack@google.com>
Cr-Commit-Position: refs/heads/master@{#833515}
parent 33519d1b
...@@ -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:
......
...@@ -102,10 +102,6 @@ void HoldingSpaceFileSystemDelegate::Init() { ...@@ -102,10 +102,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);
...@@ -188,11 +184,17 @@ void HoldingSpaceFileSystemDelegate::OnVolumeMounted( ...@@ -188,11 +184,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(
...@@ -284,6 +286,15 @@ void HoldingSpaceFileSystemDelegate::RemoveWatch( ...@@ -284,6 +286,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;
...@@ -77,6 +76,10 @@ class HoldingSpaceFileSystemDelegate : public HoldingSpaceKeyedServiceDelegate, ...@@ -77,6 +76,10 @@ class HoldingSpaceFileSystemDelegate : public HoldingSpaceKeyedServiceDelegate,
void AddWatch(const base::FilePath& file_path); void AddWatch(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.
...@@ -70,10 +85,13 @@ base::FilePath GetDownloadsPath(Profile* profile) { ...@@ -70,10 +85,13 @@ 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( Profile* profile,
"%s.txt", base::UnguessableToken::Create().ToString().c_str()); const base::FilePath& root_path,
const base::FilePath path = GetDownloadsPath(profile).Append(relative_path); const base::Optional<std::string>& 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 +102,128 @@ base::FilePath CreateTextFile(Profile* profile) { ...@@ -84,72 +102,128 @@ 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::StrictMock<MockHoldingSpaceModelObserver> mock; testing::NiceMock<MockHoldingSpaceModelObserver> mock;
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver> observer{&mock}; base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
observer.Add(holding_space_model); observer{&mock};
observer.Observe(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); // Explicitly flush the message loop after a holding space `item` is
run_loop.Quit(); // added to give file system watchers a chance to register.
}); FlushMessageLoop();
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,24 +231,149 @@ class HoldingSpaceKeyedServiceBrowserTest : public InProcessBrowserTest { ...@@ -157,24 +231,149 @@ 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(
browser()->profile(), 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 = AddHoldingSpaceItem(browser()->profile()); const auto* holding_space_item = AddHoldingSpaceItem(
browser()->profile(),
CreateTextFile(browser()->profile(), GetTestMountPoint(),
/*relative_path=*/base::nullopt));
RemoveHoldingSpaceItemViaClosure( RemoveHoldingSpaceItemViaClosure(
holding_space_item, base::BindLambdaForTesting([&]() { holding_space_item, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking; base::ScopedAllowBlockingForTesting allow_blocking;
...@@ -184,16 +383,67 @@ IN_PROC_BROWSER_TEST_F(HoldingSpaceKeyedServiceBrowserTest, ...@@ -184,16 +383,67 @@ IN_PROC_BROWSER_TEST_F(HoldingSpaceKeyedServiceBrowserTest,
{ {
// Verify that items are removed when their backing files are moved. // Verify that items are removed when their backing files are moved.
const auto* holding_space_item = AddHoldingSpaceItem(browser()->profile()); const auto* holding_space_item = AddHoldingSpaceItem(
browser()->profile(),
CreateTextFile(browser()->profile(), GetTestMountPoint(),
/*relative_path=*/base::nullopt));
RemoveHoldingSpaceItemViaClosure( RemoveHoldingSpaceItemViaClosure(
holding_space_item, base::BindLambdaForTesting([&]() { holding_space_item, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking; base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::Move( EXPECT_TRUE(
holding_space_item->file_path(), base::Move(holding_space_item->file_path(),
GetDownloadsPath(browser()->profile()) GetTestMountPoint().Append(
.Append(base::UnguessableToken::Create().ToString()))); base::UnguessableToken::Create().ToString())));
})); }));
} }
} }
IN_PROC_BROWSER_TEST_P(HoldingSpaceKeyedServiceBrowserTest,
ItemsNotRemovedDuringSuspend) {
const auto* holding_space_item =
AddHoldingSpaceItem(browser()->profile(), GetPredefinedTestFile(0));
auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
EXPECT_TRUE(holding_space_model);
ASSERT_TRUE(holding_space_model->GetItem(holding_space_item->id()));
const std::string item_id = holding_space_item->id();
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