#include "extensions/browser/api/system_info/system_info_api.h"
#include <string>
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "components/storage_monitor/storage_info.h"
#include "components/storage_monitor/storage_monitor.h"
#include "components/storage_monitor/test_storage_monitor.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "extensions/browser/api/system_info/system_info_provider.h"
#include "extensions/browser/display_info_provider_base.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/mock_extension_system.h"
#include "extensions/browser/test_extensions_browser_client.h"
#include "extensions/common/api/system_display.h"
#include "extensions/common/api/system_storage.h"
#include "extensions/common/extension_id.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
const char kFakeExtensionId[] = "extension_id";
const char kFakeExtensionId2[] = "extension_id_2";
class FakeExtensionsBrowserClient : public TestExtensionsBrowserClient {
public:
struct Broadcast {
Broadcast(events::HistogramValue histogram_value,
base::Value::List args,
bool dispatch_to_off_the_record_profiles)
: histogram_value(histogram_value),
args(std::move(args)),
dispatch_to_off_the_record_profiles(
dispatch_to_off_the_record_profiles) {}
Broadcast(Broadcast&&) = default;
~Broadcast() = default;
events::HistogramValue histogram_value;
base::Value::List args;
bool dispatch_to_off_the_record_profiles;
};
bool IsValidContext(void* context) override {
return TestExtensionsBrowserClient::IsValidContext(context) ||
context == second_context_;
}
content::BrowserContext* GetOriginalContext(
content::BrowserContext* context) override {
if (context == second_context_)
return second_context_;
return TestExtensionsBrowserClient::GetOriginalContext(context);
}
void BroadcastEventToRenderers(
events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List args,
bool dispatch_to_off_the_record_profiles) override {
event_name_to_broadcasts_map_[event_name].emplace_back(
histogram_value, std::move(args), dispatch_to_off_the_record_profiles);
}
void SetSecondContext(content::BrowserContext* second_context) {
DCHECK(!second_context_);
DCHECK(second_context);
DCHECK(!second_context->IsOffTheRecord());
second_context_ = second_context;
}
const std::vector<Broadcast>& GetBroadcastsForEvent(
const std::string& event_name) {
return event_name_to_broadcasts_map_[event_name];
}
private:
raw_ptr<content::BrowserContext> second_context_ = nullptr;
base::flat_map<std::string, std::vector<Broadcast>>
event_name_to_broadcasts_map_;
};
class FakeDisplayInfoProvider : public DisplayInfoProviderBase {
public:
FakeDisplayInfoProvider() = default;
~FakeDisplayInfoProvider() override = default;
void StartObserving() override { is_observing_ = true; }
void StopObserving() override { is_observing_ = false; }
bool is_observing() const { return is_observing_; }
private:
bool is_observing_ = false;
};
EventRouter* CreateAndUsePreflessEventRouter(content::BrowserContext* context) {
return static_cast<EventRouter*>(
EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
context, base::BindRepeating([](content::BrowserContext* context) {
return static_cast<std::unique_ptr<KeyedService>>(
std::make_unique<EventRouter>(context,
nullptr ));
})));
}
const std::string& GetFakeStorageDeviceId() {
static const base::NoDestructor<std::string> id([] {
return storage_monitor::StorageInfo::MakeDeviceId(
storage_monitor::StorageInfo::Type::REMOVABLE_MASS_STORAGE_WITH_DCIM,
"storage_device_id");
}());
return *id;
}
const storage_monitor::StorageInfo& GetFakeStorageInfo() {
static const base::NoDestructor<storage_monitor::StorageInfo> info([] {
return storage_monitor::StorageInfo(
GetFakeStorageDeviceId(),
base::FilePath::StringType() ,
std::u16string() , std::u16string() ,
std::u16string() , 0 );
}());
return *info;
}
base::Value::List GetStorageAttachedArgs() {
api::system_storage::StorageUnitInfo unit;
systeminfo::BuildStorageUnitInfo(GetFakeStorageInfo(), &unit);
base::Value::List args;
args.Append(unit.ToValue());
return args;
}
base::Value::List GetStorageDetachedArgs() {
base::Value::List args;
args.Append(
storage_monitor::StorageMonitor::GetInstance()->GetTransientIdForDeviceId(
GetFakeStorageDeviceId()));
return args;
}
}
class SystemInfoAPITest : public testing::Test {
protected:
enum class EventType { kDisplay, kStorageAttached, kStorageDetached };
SystemInfoAPITest() = default;
~SystemInfoAPITest() override = default;
void SetUp() override {
client_.SetMainContext(&context1_);
client_.SetSecondContext(&context2_);
ExtensionsBrowserClient::Set(&client_);
client_.set_extension_system_factory(&factory_);
BrowserContextDependencyManager::GetInstance()
->CreateBrowserContextServicesForTest(&context1_);
BrowserContextDependencyManager::GetInstance()
->CreateBrowserContextServicesForTest(&context2_);
router1_ = CreateAndUsePreflessEventRouter(&context1_);
router2_ = CreateAndUsePreflessEventRouter(&context2_);
FakeDisplayInfoProvider::InitializeForTesting(&display_info_provider_);
storage_monitor_ = storage_monitor::TestStorageMonitor::CreateAndInstall();
render_process_host_ =
std::make_unique<content::MockRenderProcessHost>(&context1_);
}
void TearDown() override {
storage_monitor_ = nullptr;
storage_monitor::StorageMonitor::Destroy();
DisplayInfoProvider::ResetForTesting();
router2_ = nullptr;
router1_ = nullptr;
BrowserContextDependencyManager::GetInstance()
->DestroyBrowserContextServices(&context2_);
BrowserContextDependencyManager::GetInstance()
->DestroyBrowserContextServices(&context1_);
ExtensionsBrowserClient::Set(nullptr);
render_process_host_.reset();
}
content::RenderProcessHost* render_process_host() const {
return render_process_host_.get();
}
std::string EventTypeToName(EventType type) {
switch (type) {
case EventType::kDisplay:
return api::system_display::OnDisplayChanged::kEventName;
case EventType::kStorageAttached:
return api::system_storage::OnAttached::kEventName;
case EventType::kStorageDetached:
return api::system_storage::OnDetached::kEventName;
}
}
void AddEventListener(EventRouter* router,
EventType type,
const ExtensionId& extension_id = kFakeExtensionId) {
router->AddEventListener(EventTypeToName(type), render_process_host(),
extension_id);
}
void RemoveEventListener(EventRouter* router,
EventType type,
const ExtensionId& extension_id = kFakeExtensionId) {
router->RemoveEventListener(EventTypeToName(type), render_process_host(),
extension_id);
}
bool IsDispatchingDisplayEvents() {
return display_info_provider_.is_observing();
}
bool IsDispatchingStorageAttachedEvents() {
size_t num_events_before =
client_
.GetBroadcastsForEvent(api::system_storage::OnAttached::kEventName)
.size();
storage_monitor_->receiver()->ProcessAttach(GetFakeStorageInfo());
base::RunLoop().RunUntilIdle();
const std::vector<FakeExtensionsBrowserClient::Broadcast>& broadcasts =
client_.GetBroadcastsForEvent(
api::system_storage::OnAttached::kEventName);
size_t num_events_after = broadcasts.size();
if (num_events_after != num_events_before + 1)
return false;
return broadcasts.back().histogram_value ==
events::SYSTEM_STORAGE_ON_ATTACHED &&
broadcasts.back().args == GetStorageAttachedArgs() &&
!broadcasts.back().dispatch_to_off_the_record_profiles;
}
bool IsDispatchingStorageDetachedEvents() {
size_t num_events_before =
client_
.GetBroadcastsForEvent(api::system_storage::OnDetached::kEventName)
.size();
storage_monitor_->receiver()->ProcessDetach(GetFakeStorageDeviceId());
base::RunLoop().RunUntilIdle();
const std::vector<FakeExtensionsBrowserClient::Broadcast>& broadcasts =
client_.GetBroadcastsForEvent(
api::system_storage::OnDetached::kEventName);
size_t num_events_after = broadcasts.size();
if (num_events_after != num_events_before + 1)
return false;
return broadcasts.back().histogram_value ==
events::SYSTEM_STORAGE_ON_DETACHED &&
broadcasts.back().args == GetStorageDetachedArgs() &&
!broadcasts.back().dispatch_to_off_the_record_profiles;
}
content::BrowserTaskEnvironment task_environment_;
content::TestBrowserContext context1_;
content::TestBrowserContext context2_;
FakeExtensionsBrowserClient client_;
MockExtensionSystemFactory<MockExtensionSystem> factory_;
raw_ptr<EventRouter> router1_ = nullptr;
raw_ptr<EventRouter> router2_ = nullptr;
FakeDisplayInfoProvider display_info_provider_;
raw_ptr<storage_monitor::TestStorageMonitor> storage_monitor_;
std::unique_ptr<content::RenderProcessHost> render_process_host_;
};
TEST_F(SystemInfoAPITest, DisplayListener_AddRemove) {
EXPECT_FALSE(IsDispatchingDisplayEvents());
AddEventListener(router1_, EventType::kDisplay);
SystemInfoAPI api1(&context1_);
EXPECT_TRUE(IsDispatchingDisplayEvents());
RemoveEventListener(router1_, EventType::kDisplay);
EXPECT_FALSE(IsDispatchingDisplayEvents());
AddEventListener(router1_, EventType::kDisplay);
EXPECT_TRUE(IsDispatchingDisplayEvents());
api1.Shutdown();
EXPECT_FALSE(IsDispatchingDisplayEvents());
}
TEST_F(SystemInfoAPITest, DisplayListener_MultipleContexts) {
SystemInfoAPI api1(&context1_);
SystemInfoAPI api2(&context2_);
EXPECT_FALSE(IsDispatchingDisplayEvents());
AddEventListener(router1_, EventType::kDisplay);
EXPECT_TRUE(IsDispatchingDisplayEvents());
AddEventListener(router2_, EventType::kDisplay);
EXPECT_TRUE(IsDispatchingDisplayEvents());
RemoveEventListener(router1_, EventType::kDisplay);
EXPECT_TRUE(IsDispatchingDisplayEvents());
RemoveEventListener(router2_, EventType::kDisplay);
EXPECT_FALSE(IsDispatchingDisplayEvents());
AddEventListener(router1_, EventType::kDisplay);
EXPECT_TRUE(IsDispatchingDisplayEvents());
api2.Shutdown();
EXPECT_TRUE(IsDispatchingDisplayEvents());
api1.Shutdown();
EXPECT_FALSE(IsDispatchingDisplayEvents());
}
TEST_F(SystemInfoAPITest, DisplayListener_MultipleListeners) {
SystemInfoAPI api1(&context1_);
EXPECT_FALSE(IsDispatchingDisplayEvents());
AddEventListener(router1_, EventType::kDisplay);
EXPECT_TRUE(IsDispatchingDisplayEvents());
AddEventListener(router1_, EventType::kDisplay, kFakeExtensionId2);
EXPECT_TRUE(IsDispatchingDisplayEvents());
RemoveEventListener(router1_, EventType::kDisplay);
EXPECT_TRUE(IsDispatchingDisplayEvents());
RemoveEventListener(router1_, EventType::kDisplay, kFakeExtensionId2);
EXPECT_FALSE(IsDispatchingDisplayEvents());
api1.Shutdown();
}
TEST_F(SystemInfoAPITest, StorageListener_AddRemove) {
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
AddEventListener(router1_, EventType::kStorageAttached);
SystemInfoAPI api1(&context1_);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
AddEventListener(router1_, EventType::kStorageDetached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
RemoveEventListener(router1_, EventType::kStorageDetached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
RemoveEventListener(router1_, EventType::kStorageAttached);
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
AddEventListener(router1_, EventType::kStorageAttached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
api1.Shutdown();
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
}
TEST_F(SystemInfoAPITest, StorageListener_MultipleContexts) {
SystemInfoAPI api1(&context1_);
SystemInfoAPI api2(&context2_);
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
AddEventListener(router1_, EventType::kStorageAttached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
AddEventListener(router2_, EventType::kStorageAttached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
RemoveEventListener(router1_, EventType::kStorageAttached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
RemoveEventListener(router2_, EventType::kStorageAttached);
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
AddEventListener(router1_, EventType::kStorageAttached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
api2.Shutdown();
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
api1.Shutdown();
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
}
TEST_F(SystemInfoAPITest, StorageListener_MultipleListeners) {
SystemInfoAPI api1(&context1_);
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
AddEventListener(router1_, EventType::kStorageAttached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
AddEventListener(router1_, EventType::kStorageAttached, kFakeExtensionId2);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
RemoveEventListener(router1_, EventType::kStorageAttached);
EXPECT_TRUE(IsDispatchingStorageAttachedEvents());
EXPECT_TRUE(IsDispatchingStorageDetachedEvents());
RemoveEventListener(router1_, EventType::kStorageAttached, kFakeExtensionId2);
EXPECT_FALSE(IsDispatchingStorageAttachedEvents());
EXPECT_FALSE(IsDispatchingStorageDetachedEvents());
api1.Shutdown();
}
}