Commit 43f48aee authored by David Black's avatar David Black Committed by Commit Bot

Adds HoldingSpaceFileSystemDelegate.

This delegate is the first of the `HoldingSpaceKeyedServiceDelegates` to
be added. A follow up CL will refactor existing service logic into a
`HoldingSpaceDownloadsDelegate` and `HoldingSpacePersistenceDelegate`.

The delegate added in this CL is tasked with watching the local file
system for changes to files backing holding space items. It notifies the
service on file removal so that it can perform model updates.

Currently, a watcher is added per file but this will later be changed
to a single watcher per directory. This CL is mostly concerned with
getting things in place and additional optimization can come later.

Bug: 1122418
Change-Id: I9a796bd1318268a62e0c7227a9a299b566cc2ffc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2382399
Commit-Queue: David Black <dmblack@google.com>
Reviewed-by: default avatarToni Baržić <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#802863}
parent 033b1fe2
......@@ -43,6 +43,14 @@ void HoldingSpaceModel::RemoveItem(const std::string& id) {
observer.OnHoldingSpaceItemRemoved(item.get());
}
void HoldingSpaceModel::RemoveIf(Predicate predicate) {
for (int i = items_.size() - 1; i >= 0; --i) {
const HoldingSpaceItem* item = items_.at(i).get();
if (predicate.Run(item))
RemoveItem(item->id());
}
}
void HoldingSpaceModel::RemoveAll() {
// Clear the item list, but keep the items around until the observers have
// been notified of the item removal.
......
......@@ -10,6 +10,7 @@
#include <vector>
#include "ash/public/cpp/ash_public_export.h"
#include "base/callback_forward.h"
#include "base/observer_list.h"
namespace ash {
......@@ -39,6 +40,11 @@ class ASH_PUBLIC_EXPORT HoldingSpaceModel {
// Removes a single holding space item from the model.
void RemoveItem(const std::string& id);
// Removes all holding space items from the model for which the specified
// `predicate` returns true.
using Predicate = base::RepeatingCallback<bool(const HoldingSpaceItem*)>;
void RemoveIf(Predicate predicate);
// Removes all the items from the model.
void RemoveAll();
......
......@@ -1870,8 +1870,12 @@ static_library("ui") {
"ash/clipboard_util.h",
"ash/holding_space/holding_space_client_impl.cc",
"ash/holding_space/holding_space_client_impl.h",
"ash/holding_space/holding_space_file_system_delegate.cc",
"ash/holding_space/holding_space_file_system_delegate.h",
"ash/holding_space/holding_space_keyed_service.cc",
"ash/holding_space/holding_space_keyed_service.h",
"ash/holding_space/holding_space_keyed_service_delegate.cc",
"ash/holding_space/holding_space_keyed_service_delegate.h",
"ash/holding_space/holding_space_keyed_service_factory.cc",
"ash/holding_space/holding_space_keyed_service_factory.h",
"ash/holding_space/holding_space_thumbnail_loader.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/sequence_checker.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
namespace ash {
// HoldingSpaceFileSystemDelegate::FileSystemWatcher ---------------------------
class HoldingSpaceFileSystemDelegate::FileSystemWatcher {
public:
explicit FileSystemWatcher(base::FilePathWatcher::Callback callback)
: callback_(callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DETACH_FROM_SEQUENCE(sequence_checker_);
}
FileSystemWatcher(const FileSystemWatcher&) = delete;
FileSystemWatcher& operator=(const FileSystemWatcher&) = delete;
~FileSystemWatcher() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
void AddWatch(const base::FilePath& file_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::Contains(watchers_, file_path))
return;
watchers_[file_path] = std::make_unique<base::FilePathWatcher>();
watchers_[file_path]->Watch(
file_path, /*recursive=*/false,
base::Bind(&FileSystemWatcher::OnFilePathChanged,
weak_factory_.GetWeakPtr()));
}
void RemoveWatch(const base::FilePath& file_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
watchers_.erase(file_path);
}
base::WeakPtr<FileSystemWatcher> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
void OnFilePathChanged(const base::FilePath& file_path, bool error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(callback_, file_path, error));
}
SEQUENCE_CHECKER(sequence_checker_);
base::FilePathWatcher::Callback callback_;
std::map<base::FilePath, std::unique_ptr<base::FilePathWatcher>> watchers_;
base::WeakPtrFactory<FileSystemWatcher> weak_factory_{this};
};
// HoldingSpaceFileSystemDelegate ----------------------------------------------
HoldingSpaceFileSystemDelegate::HoldingSpaceFileSystemDelegate(
HoldingSpaceModel* model,
FileRemovedCallback file_removed_callback)
: HoldingSpaceKeyedServiceDelegate(model),
file_removed_callback_(file_removed_callback),
file_system_watcher_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
file_system_watcher_ = std::make_unique<FileSystemWatcher>(
base::Bind(&HoldingSpaceFileSystemDelegate::OnFilePathChanged,
weak_factory_.GetWeakPtr()));
}
HoldingSpaceFileSystemDelegate::~HoldingSpaceFileSystemDelegate() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
weak_factory_.InvalidateWeakPtrs();
file_system_watcher_runner_->DeleteSoon(FROM_HERE,
file_system_watcher_.release());
}
// TODO(dmblack): Watch `item`'s parent directory instead of its backing file so
// that we can reuse the same watcher across multiple holding space items.
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemAdded(
const HoldingSpaceItem* item) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
AddWatch(item->file_path());
}
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemRemoved(
const HoldingSpaceItem* item) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
RemoveWatch(item->file_path());
}
void HoldingSpaceFileSystemDelegate::OnFilePathChanged(
const base::FilePath& file_path,
bool error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!error);
// Currently `OnFilePathChanged()` is only called on removal of `file_path`.
file_removed_callback_.Run(file_path);
}
void HoldingSpaceFileSystemDelegate::AddWatch(const base::FilePath& file_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
file_system_watcher_runner_->PostTask(
FROM_HERE, base::BindOnce(&FileSystemWatcher::AddWatch,
file_system_watcher_->GetWeakPtr(), file_path));
}
void HoldingSpaceFileSystemDelegate::RemoveWatch(
const base::FilePath& file_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
file_system_watcher_runner_->PostTask(
FROM_HERE, base::BindOnce(&FileSystemWatcher::RemoveWatch,
file_system_watcher_->GetWeakPtr(), file_path));
}
} // namespace ash
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_FILE_SYSTEM_DELEGATE_H_
#define CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_FILE_SYSTEM_DELEGATE_H_
#include <memory>
#include "base/callback.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h"
namespace base {
class FilePath;
} // namespace base
namespace ash {
// A delegate of `HoldingSpaceKeyedService` tasked with monitoring the file
// system for removal of files backing holding space items.
class HoldingSpaceFileSystemDelegate : public HoldingSpaceKeyedServiceDelegate {
public:
// Callback to be invoked when a watched file path is removed. The delegate
// will watch file paths for all holding space items in the model.
using FileRemovedCallback =
base::RepeatingCallback<void(const base::FilePath&)>;
HoldingSpaceFileSystemDelegate(HoldingSpaceModel* model,
FileRemovedCallback file_removed_callback);
HoldingSpaceFileSystemDelegate(const HoldingSpaceFileSystemDelegate&) =
delete;
HoldingSpaceFileSystemDelegate& operator=(
const HoldingSpaceFileSystemDelegate&) = delete;
~HoldingSpaceFileSystemDelegate() override;
private:
class FileSystemWatcher;
// HoldingSpaceKeyedServiceDelegate:
void OnHoldingSpaceItemAdded(const HoldingSpaceItem* item) override;
void OnHoldingSpaceItemRemoved(const HoldingSpaceItem* item) override;
// Invoked when the specified `file_path` has changed.
void OnFilePathChanged(const base::FilePath& file_path, bool error);
// Adds/removes a watch for the specified `file_path`.
void AddWatch(const base::FilePath& file_path);
void RemoveWatch(const base::FilePath& file_path);
// Callback to invoke when file removal is detected.
FileRemovedCallback file_removed_callback_;
// The `file_system_watcher_` is tasked with watching the file system for
// changes on behalf of the delegate. It does so on a non-UI sequence. As
// such, all communication with `file_system_watcher_` must be posted via the
// `file_system_watcher_runner_`. In return, the `file_system_watcher_` will
// post its responses back onto the UI thread.
std::unique_ptr<FileSystemWatcher> file_system_watcher_;
scoped_refptr<base::SequencedTaskRunner> file_system_watcher_runner_;
base::WeakPtrFactory<HoldingSpaceFileSystemDelegate> weak_factory_{this};
};
} // namespace ash
#endif // CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_FILE_SYSTEM_DELEGATE_H_
......@@ -13,6 +13,7 @@
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_util.h"
#include "components/account_id/account_id.h"
#include "components/pref_registry/pref_registry_syncable.h"
......@@ -45,6 +46,12 @@ HoldingSpaceKeyedService::HoldingSpaceKeyedService(
account_id_(account_id),
holding_space_client_(Profile::FromBrowserContext(context)),
thumbnail_loader_(Profile::FromBrowserContext(context)) {
// TODO(dmblack): Add delegates for downloads and persistence.
delegates_.push_back(std::make_unique<HoldingSpaceFileSystemDelegate>(
&holding_space_model_,
base::BindRepeating(&HoldingSpaceKeyedService::OnFileRemoved,
weak_factory_.GetWeakPtr())));
// If the service's associated profile is ready, we can proceed to restore the
// `holding_space_model_` from persistence.
ProfileManager* const profile_manager = GetProfileManager();
......@@ -284,4 +291,13 @@ gfx::ImageSkia HoldingSpaceKeyedService::ResolveImage(
return GetIconForPath(file_path);
}
void HoldingSpaceKeyedService::OnFileRemoved(const base::FilePath& file_path) {
// When `file_path` is removed, we need to remove any associated items.
holding_space_model_.RemoveIf(base::BindRepeating(
[](const base::FilePath& file_path, const HoldingSpaceItem* item) {
return item->file_path() == file_path;
},
std::cref(file_path)));
}
} // namespace ash
......@@ -15,6 +15,7 @@
#include "chrome/browser/profiles/profile_manager.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_keyed_service_delegate.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader.h"
#include "components/account_id/account_id.h"
#include "components/download/public/common/download_item.h"
......@@ -42,7 +43,7 @@ class ImageSkia;
namespace storage {
class FileSystemURL;
}
} // namespace storage
namespace ash {
......@@ -104,12 +105,12 @@ class HoldingSpaceKeyedService : public KeyedService,
return &holding_space_model_;
}
void SetDownloadManagerForTesting(content::DownloadManager* manager);
HoldingSpaceThumbnailLoader* thumbnail_loader_for_testing() {
return &thumbnail_loader_;
}
void SetDownloadManagerForTesting(content::DownloadManager* manager);
private:
// KeyedService:
void Shutdown() override;
......@@ -141,10 +142,13 @@ class HoldingSpaceKeyedService : public KeyedService,
std::vector<HoldingSpaceItemPtr> non_existing_items);
void OnModelRestored();
// Resolves file attributes from a file path;
// Resolves file attributes from a `file_path`;
GURL ResolveFileSystemUrl(const base::FilePath& file_path) const;
gfx::ImageSkia ResolveImage(const base::FilePath& file_path) const;
// Invoked when the specified `file_path` is removed.
void OnFileRemoved(const base::FilePath& file_path);
content::BrowserContext* const browser_context_;
const AccountId account_id_;
......@@ -153,6 +157,11 @@ class HoldingSpaceKeyedService : public KeyedService,
HoldingSpaceThumbnailLoader thumbnail_loader_;
// The `HoldingSpaceKeyedService` owns a collection of `delegates_` which are
// each tasked with an independent area of responsibility on behalf of the
// service. They operate autonomously of one another.
std::vector<std::unique_ptr<HoldingSpaceKeyedServiceDelegate>> delegates_;
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver>
holding_space_model_observer_{this};
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/holding_space/holding_space_controller.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "ash/public/cpp/holding_space/holding_space_model_observer.h"
#include "base/files/file_path.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/unguessable_token.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image_skia.h"
namespace ash {
namespace {
// Mocks -----------------------------------------------------------------------
// Mock observer which can be used to set expectations about model behavior.
class MockHoldingSpaceModelObserver : public HoldingSpaceModelObserver {
public:
MOCK_METHOD(void,
OnHoldingSpaceItemAdded,
(const HoldingSpaceItem* item),
(override));
MOCK_METHOD(void,
OnHoldingSpaceItemRemoved,
(const HoldingSpaceItem* item),
(override));
};
// 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`.
base::FilePath GetDownloadsPath(Profile* profile) {
base::FilePath result;
EXPECT_TRUE(
storage::ExternalMountPoints::GetSystemInstance()->GetRegisteredPath(
file_manager::util::GetDownloadsMountPointName(profile), &result));
return result;
}
// Creates a txt file at the path of the downloads mount point for `profile`.
base::FilePath CreateTextFile(Profile* profile) {
const std::string relative_path = base::StringPrintf(
"%s.txt", base::UnguessableToken::Create().ToString().c_str());
const base::FilePath path = GetDownloadsPath(profile).Append(relative_path);
base::ScopedAllowBlockingForTesting allow_blocking;
if (!base::CreateDirectory(path.DirName()))
return base::FilePath();
if (!base::WriteFile(path, /*content=*/std::string()))
return base::FilePath();
return path;
}
// Adds a holding space item backed by a txt file at the path of the downloads
// mount point for `profile`. A pointer to the added item is returned.
const HoldingSpaceItem* AddHoldingSpaceItem(Profile* profile) {
EXPECT_TRUE(ash::HoldingSpaceController::Get());
auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
EXPECT_TRUE(holding_space_model);
const HoldingSpaceItem* result = nullptr;
testing::StrictMock<MockHoldingSpaceModelObserver> mock;
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver> observer{&mock};
observer.Add(holding_space_model);
base::RunLoop run_loop;
EXPECT_CALL(mock, OnHoldingSpaceItemAdded)
.WillOnce([&](const HoldingSpaceItem* item) {
result = item;
// Explicitly flush the message loop after a holding space `item` is
// added to give file system watchers a chance to register.
FlushMessageLoop();
run_loop.Quit();
});
holding_space_model->AddItem(HoldingSpaceItem::CreateFileBackedItem(
HoldingSpaceItem::Type::kDownload, CreateTextFile(profile), GURL(),
gfx::ImageSkia()));
run_loop.Run();
return result;
}
// 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());
auto* holding_space_model = ash::HoldingSpaceController::Get()->model();
EXPECT_TRUE(holding_space_model);
testing::StrictMock<MockHoldingSpaceModelObserver> mock;
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver> observer{&mock};
observer.Add(holding_space_model);
base::RunLoop run_loop;
EXPECT_CALL(mock, OnHoldingSpaceItemRemoved)
.WillOnce([&](const HoldingSpaceItem* item) {
EXPECT_EQ(holding_space_item, item);
run_loop.Quit();
});
std::move(closure).Run();
run_loop.Run();
}
} // namespace
// HoldingSpaceKeyedServiceBrowserTest -----------------------------------------
class HoldingSpaceKeyedServiceBrowserTest : public InProcessBrowserTest {
public:
HoldingSpaceKeyedServiceBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kTemporaryHoldingSpace);
}
// InProcessBrowserTest:
void SetUpInProcessBrowserTestFixture() override {
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests -----------------------------------------------------------------------
// Verifies that holding space items are removed when their backing files
// "disappear". Note that a "disappearance" could be due to file move or delete.
IN_PROC_BROWSER_TEST_F(HoldingSpaceKeyedServiceBrowserTest,
RemovesItemsWhenBackingFileDisappears) {
{
// Verify that items are removed when their backing files are deleted.
const auto* holding_space_item = AddHoldingSpaceItem(browser()->profile());
RemoveHoldingSpaceItemViaClosure(
holding_space_item, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::DeleteFile(holding_space_item->file_path()));
}));
}
{
// Verify that items are removed when their backing files are moved.
const auto* holding_space_item = AddHoldingSpaceItem(browser()->profile());
RemoveHoldingSpaceItemViaClosure(
holding_space_item, base::BindLambdaForTesting([&]() {
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::Move(
holding_space_item->file_path(),
GetDownloadsPath(browser()->profile())
.Append(base::UnguessableToken::Create().ToString())));
}));
}
}
} // namespace ash
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h"
namespace ash {
HoldingSpaceKeyedServiceDelegate::~HoldingSpaceKeyedServiceDelegate() = default;
HoldingSpaceKeyedServiceDelegate::HoldingSpaceKeyedServiceDelegate(
HoldingSpaceModel* model)
: model_(model) {
holding_space_model_observer_.Add(model);
}
void HoldingSpaceKeyedServiceDelegate::OnHoldingSpaceItemAdded(
const HoldingSpaceItem* item) {}
void HoldingSpaceKeyedServiceDelegate::OnHoldingSpaceItemRemoved(
const HoldingSpaceItem* item) {}
} // namespace ash
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_KEYED_SERVICE_DELEGATE_H_
#define CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_KEYED_SERVICE_DELEGATE_H_
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "ash/public/cpp/holding_space/holding_space_model_observer.h"
#include "base/scoped_observer.h"
namespace ash {
// Abstract class for a delegate of `HoldingSpaceKeyedService`. Multiple
// delegates will exist, each with an independent area of responsibility.
class HoldingSpaceKeyedServiceDelegate : public HoldingSpaceModelObserver {
public:
~HoldingSpaceKeyedServiceDelegate() override;
protected:
explicit HoldingSpaceKeyedServiceDelegate(HoldingSpaceModel* model);
// Returns the holding space model owned by `HoldingSpaceKeyedService`.
const HoldingSpaceModel* model() const { return model_; }
private:
// HoldingSpaceModelObserver:
void OnHoldingSpaceItemAdded(const HoldingSpaceItem* item) override;
void OnHoldingSpaceItemRemoved(const HoldingSpaceItem* item) override;
// Owned by `HoldingSpaceKeyedService`.
const HoldingSpaceModel* const model_;
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver>
holding_space_model_observer_{this};
};
} // namespace ash
#endif // CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_KEYED_SERVICE_DELEGATE_H_
......@@ -1613,6 +1613,7 @@ if (!is_android) {
"../browser/policy/system_features_policy_browsertest.cc",
"../browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc",
"../browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc",
"../browser/ui/ash/holding_space/holding_space_keyed_service_browsertest.cc",
"../browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc",
]
}
......
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