Commit a80e4d7d authored by Istiaque Ahmed's avatar Istiaque Ahmed Committed by Commit Bot

[Extensions] Extensively expand EventListenerMap/EventRouter tests for workers.

This CL primarily expands EventListenerMap tests to account for
service workers. It does so by parameterizing EventListenerMapTest
with service worker / non-service worker param.

Bug: None
Change-Id: I11a657d1b5d9293d73c1c6546e81bede36319ee8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2247548
Commit-Queue: Istiaque Ahmed <lazyboy@chromium.org>
Reviewed-by: default avatarDavid Bertoni <dbertoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#779195}
parent 57bdd8d3
...@@ -29,6 +29,19 @@ const char kEvent1Name[] = "event1"; ...@@ -29,6 +29,19 @@ const char kEvent1Name[] = "event1";
const char kEvent2Name[] = "event2"; const char kEvent2Name[] = "event2";
const char kURL[] = "https://google.com/some/url"; const char kURL[] = "https://google.com/some/url";
// Returns appropriate worker version id for tests.
int64_t GetWorkerVersionId(bool lazy) {
static constexpr int64_t kDummyServiceWorkerVersionId = 199;
return lazy ? blink::mojom::kInvalidServiceWorkerVersionId
: kDummyServiceWorkerVersionId;
}
// Returns appropriate worker thread id for tests.
int GetWorkerThreadId(bool lazy) {
static constexpr int kDummyServiceWorkerThreadId = 99;
return lazy ? kMainThreadId : kDummyServiceWorkerThreadId;
}
using EventListenerConstructor = using EventListenerConstructor =
base::RepeatingCallback<std::unique_ptr<EventListener>( base::RepeatingCallback<std::unique_ptr<EventListener>(
const std::string& /* event_name */, const std::string& /* event_name */,
...@@ -89,10 +102,16 @@ class EventListenerMapTest : public ExtensionsTest { ...@@ -89,10 +102,16 @@ class EventListenerMapTest : public ExtensionsTest {
std::unique_ptr<EventListener> CreateLazyListener( std::unique_ptr<EventListener> CreateLazyListener(
const std::string& event_name, const std::string& event_name,
const ExtensionId& extension_id, const ExtensionId& extension_id,
const std::string& host_suffix, std::unique_ptr<base::DictionaryValue> filter,
bool is_for_service_worker) { bool is_for_service_worker) {
if (is_for_service_worker) {
return EventListener::ForExtensionServiceWorker(
event_name, extension_id, nullptr,
Extension::GetBaseURLFromExtensionId(extension_id),
GetWorkerVersionId(true), GetWorkerThreadId(true), std::move(filter));
}
return EventListener::ForExtension(event_name, extension_id, nullptr, return EventListener::ForExtension(event_name, extension_id, nullptr,
CreateHostSuffixFilter(host_suffix)); std::move(filter));
} }
protected: protected:
...@@ -109,6 +128,11 @@ class EventListenerMapTest : public ExtensionsTest { ...@@ -109,6 +128,11 @@ class EventListenerMapTest : public ExtensionsTest {
content::RenderProcessHost* process_; content::RenderProcessHost* process_;
}; };
// Parameterized version of test to run with |is_for_service_worker|.
class EventListenerMapWithContextTest
: public EventListenerMapTest,
public testing::WithParamInterface<bool /* is_for_service_worker */> {};
std::unique_ptr<EventListener> CreateEventListenerForExtension( std::unique_ptr<EventListener> CreateEventListenerForExtension(
const std::string& extension_id, const std::string& extension_id,
const std::string& event_name, const std::string& event_name,
...@@ -127,6 +151,17 @@ std::unique_ptr<EventListener> CreateEventListenerForURL( ...@@ -127,6 +151,17 @@ std::unique_ptr<EventListener> CreateEventListenerForURL(
std::move(filter)); std::move(filter));
} }
std::unique_ptr<EventListener> CreateEventListenerForExtensionServiceWorker(
const std::string& extension_id,
const std::string& event_name,
content::RenderProcessHost* process,
std::unique_ptr<base::DictionaryValue> filter) {
return EventListener::ForExtensionServiceWorker(
event_name, extension_id, process,
Extension::GetBaseURLFromExtensionId(extension_id),
GetWorkerVersionId(false), GetWorkerThreadId(false), std::move(filter));
}
void EventListenerMapTest::TestUnfilteredEventsGoToAllListeners( void EventListenerMapTest::TestUnfilteredEventsGoToAllListeners(
const EventListenerConstructor& constructor) { const EventListenerConstructor& constructor) {
listeners_->AddListener(constructor.Run( listeners_->AddListener(constructor.Run(
...@@ -145,31 +180,76 @@ TEST_F(EventListenerMapTest, UnfilteredEventsGoToAllListenersForURLs) { ...@@ -145,31 +180,76 @@ TEST_F(EventListenerMapTest, UnfilteredEventsGoToAllListenersForURLs) {
base::BindRepeating(&CreateEventListenerForURL, GURL(kURL))); base::BindRepeating(&CreateEventListenerForURL, GURL(kURL)));
} }
TEST_F(EventListenerMapTest,
UnfilteredEventsGoToAllListenersForExtensionServiceWorkers) {
TestUnfilteredEventsGoToAllListeners(base::BindRepeating(
&CreateEventListenerForExtensionServiceWorker, kExt1Id));
}
TEST_F(EventListenerMapTest, FilteredEventsGoToAllMatchingListeners) { TEST_F(EventListenerMapTest, FilteredEventsGoToAllMatchingListeners) {
listeners_->AddListener(EventListener::ForExtension( auto create_filter = [&](const std::string& filter_str) {
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); return CreateHostSuffixFilter(filter_str);
listeners_->AddListener(EventListener::ForExtension( };
kEvent1Name, kExt1Id, nullptr, std::make_unique<DictionaryValue>())); auto create_empty_filter = []() {
return std::make_unique<base::DictionaryValue>();
};
for (bool is_for_service_worker : {false, true}) {
listeners_->AddListener(CreateLazyListener(kEvent1Name, kExt1Id,
create_filter("google.com"),
is_for_service_worker));
listeners_->AddListener(CreateLazyListener(
kEvent1Name, kExt1Id, create_empty_filter(), is_for_service_worker));
}
std::unique_ptr<Event> event(CreateNamedEvent(kEvent1Name)); std::unique_ptr<Event> event(CreateNamedEvent(kEvent1Name));
event->filter_info.url = GURL("http://www.google.com"); event->filter_info.url = GURL("http://www.google.com");
std::set<const EventListener*> targets(listeners_->GetEventListeners(*event)); std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
ASSERT_EQ(2u, targets.size()); ASSERT_EQ(4u, targets.size());
} }
TEST_F(EventListenerMapTest, FilteredEventsOnlyGoToMatchingListeners) { TEST_P(EventListenerMapWithContextTest,
listeners_->AddListener(EventListener::ForExtension( FilteredEventsOnlyGoToMatchingListeners) {
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); const bool is_for_service_worker = GetParam();
listeners_->AddListener(EventListener::ForExtension( struct TestCase {
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("yahoo.com"))); const std::string filter_host_suffix;
const std::string url_of_event;
} kTestCases[] = {
{"google.com", "http://www.google.com"},
{"yahoo.com", "http://www.yahoo.com"},
};
for (const TestCase& test_case : kTestCases) {
listeners_->AddListener(
CreateLazyListener(kEvent1Name, kExt1Id,
CreateHostSuffixFilter(test_case.filter_host_suffix),
is_for_service_worker));
}
std::unique_ptr<Event> event(CreateNamedEvent(kEvent1Name)); // Matching filters.
event->filter_info.url = GURL("http://www.google.com"); for (const TestCase& test_case : kTestCases) {
std::set<const EventListener*> targets(listeners_->GetEventListeners(*event)); SCOPED_TRACE(base::StringPrintf("host_suffix = %s, url = %s",
ASSERT_EQ(1u, targets.size()); test_case.filter_host_suffix.c_str(),
std::unique_ptr<EventListener> listener(EventListener::ForExtension( test_case.url_of_event.c_str()));
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); std::unique_ptr<Event> event(
ASSERT_TRUE((*targets.begin())->Equals(listener.get())); CreateEvent(kEvent1Name, GURL(test_case.url_of_event)));
std::set<const EventListener*> targets(
listeners_->GetEventListeners(*event));
ASSERT_EQ(1u, targets.size());
EXPECT_TRUE(
CreateLazyListener(kEvent1Name, kExt1Id,
CreateHostSuffixFilter(test_case.filter_host_suffix),
is_for_service_worker)
->Equals(*targets.begin()));
}
// Non-matching filter.
{
std::unique_ptr<Event> event(
CreateEvent(kEvent1Name, GURL("http://does_not_match.com")));
std::set<const EventListener*> targets(
listeners_->GetEventListeners(*event));
EXPECT_TRUE(targets.empty());
}
} }
TEST_F(EventListenerMapTest, LazyAndUnlazyListenersGetReturned) { TEST_F(EventListenerMapTest, LazyAndUnlazyListenersGetReturned) {
...@@ -210,6 +290,11 @@ TEST_F(EventListenerMapTest, TestRemovingByProcessForURL) { ...@@ -210,6 +290,11 @@ TEST_F(EventListenerMapTest, TestRemovingByProcessForURL) {
base::BindRepeating(&CreateEventListenerForURL, GURL(kURL))); base::BindRepeating(&CreateEventListenerForURL, GURL(kURL)));
} }
TEST_F(EventListenerMapTest, TestRemovingByProcessForExtensionServiceWorker) {
TestRemovingByProcess(base::BindRepeating(
&CreateEventListenerForExtensionServiceWorker, kExt1Id));
}
void EventListenerMapTest::TestRemovingByListener( void EventListenerMapTest::TestRemovingByListener(
const EventListenerConstructor& constructor) { const EventListenerConstructor& constructor) {
listeners_->AddListener(constructor.Run( listeners_->AddListener(constructor.Run(
...@@ -237,14 +322,23 @@ TEST_F(EventListenerMapTest, TestRemovingByListenerForURL) { ...@@ -237,14 +322,23 @@ TEST_F(EventListenerMapTest, TestRemovingByListenerForURL) {
base::BindRepeating(&CreateEventListenerForURL, GURL(kURL))); base::BindRepeating(&CreateEventListenerForURL, GURL(kURL)));
} }
TEST_F(EventListenerMapTest, TestLazyDoubleAddIsUndoneByRemove) { TEST_F(EventListenerMapTest, TestRemovingByListenerForExtensionServiceWorker) {
listeners_->AddListener(EventListener::ForExtension( TestRemovingByListener(base::BindRepeating(
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); &CreateEventListenerForExtensionServiceWorker, kExt1Id));
listeners_->AddListener(EventListener::ForExtension( }
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com")));
std::unique_ptr<EventListener> listener(EventListener::ForExtension( TEST_P(EventListenerMapWithContextTest, TestLazyDoubleAddIsUndoneByRemove) {
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); const bool is_for_service_worker = GetParam();
listeners_->AddListener(CreateLazyListener(
kEvent1Name, kExt1Id, CreateHostSuffixFilter("google.com"),
is_for_service_worker));
listeners_->AddListener(CreateLazyListener(
kEvent1Name, kExt1Id, CreateHostSuffixFilter("google.com"),
is_for_service_worker));
std::unique_ptr<EventListener> listener = CreateLazyListener(
kEvent1Name, kExt1Id, CreateHostSuffixFilter("google.com"),
is_for_service_worker);
listeners_->RemoveListener(listener.get()); listeners_->RemoveListener(listener.get());
std::unique_ptr<Event> event(CreateNamedEvent(kEvent1Name)); std::unique_ptr<Event> event(CreateNamedEvent(kEvent1Name));
...@@ -262,10 +356,14 @@ TEST_F(EventListenerMapTest, HostSuffixFilterEquality) { ...@@ -262,10 +356,14 @@ TEST_F(EventListenerMapTest, HostSuffixFilterEquality) {
} }
TEST_F(EventListenerMapTest, RemoveListenersForExtension) { TEST_F(EventListenerMapTest, RemoveListenersForExtension) {
listeners_->AddListener(EventListener::ForExtension( for (bool is_for_service_worker : {false, true}) {
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); listeners_->AddListener(CreateLazyListener(
listeners_->AddListener(EventListener::ForExtension( kEvent1Name, kExt1Id, CreateHostSuffixFilter("google.com"),
kEvent2Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); is_for_service_worker));
listeners_->AddListener(CreateLazyListener(
kEvent2Name, kExt1Id, CreateHostSuffixFilter("google.com"),
is_for_service_worker));
}
listeners_->RemoveListenersForExtension(kExt1Id); listeners_->RemoveListenersForExtension(kExt1Id);
...@@ -281,11 +379,15 @@ TEST_F(EventListenerMapTest, RemoveListenersForExtension) { ...@@ -281,11 +379,15 @@ TEST_F(EventListenerMapTest, RemoveListenersForExtension) {
ASSERT_EQ(0u, targets.size()); ASSERT_EQ(0u, targets.size());
} }
TEST_F(EventListenerMapTest, AddExistingFilteredListener) { TEST_P(EventListenerMapWithContextTest, AddExistingFilteredListener) {
bool first_new = listeners_->AddListener(EventListener::ForExtension( const bool is_for_service_worker = GetParam();
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com")));
bool second_new = listeners_->AddListener(EventListener::ForExtension( bool first_new = listeners_->AddListener(CreateLazyListener(
kEvent1Name, kExt1Id, nullptr, CreateHostSuffixFilter("google.com"))); kEvent1Name, kExt1Id, CreateHostSuffixFilter("google.com"),
is_for_service_worker));
bool second_new = listeners_->AddListener(CreateLazyListener(
kEvent1Name, kExt1Id, CreateHostSuffixFilter("google.com"),
is_for_service_worker));
ASSERT_TRUE(first_new); ASSERT_TRUE(first_new);
ASSERT_FALSE(second_new); ASSERT_FALSE(second_new);
...@@ -319,11 +421,20 @@ TEST_F(EventListenerMapTest, AddExistingUnfilteredListenerForURLs) { ...@@ -319,11 +421,20 @@ TEST_F(EventListenerMapTest, AddExistingUnfilteredListenerForURLs) {
base::BindRepeating(&CreateEventListenerForURL, GURL(kURL))); base::BindRepeating(&CreateEventListenerForURL, GURL(kURL)));
} }
TEST_F(EventListenerMapTest,
AddExistingUnfilteredListenerForExtensionServiceWorker) {
TestAddExistingUnfilteredListener(base::BindRepeating(
&CreateEventListenerForExtensionServiceWorker, kExt1Id));
}
TEST_F(EventListenerMapTest, RemovingRouters) { TEST_F(EventListenerMapTest, RemovingRouters) {
listeners_->AddListener(EventListener::ForExtension( listeners_->AddListener(EventListener::ForExtension(
kEvent1Name, kExt1Id, process_, std::unique_ptr<DictionaryValue>())); kEvent1Name, kExt1Id, process_, std::unique_ptr<DictionaryValue>()));
listeners_->AddListener(EventListener::ForURL( listeners_->AddListener(EventListener::ForURL(
kEvent1Name, GURL(kURL), process_, std::unique_ptr<DictionaryValue>())); kEvent1Name, GURL(kURL), process_, std::unique_ptr<DictionaryValue>()));
listeners_->AddListener(CreateEventListenerForExtensionServiceWorker(
kExt1Id, kEvent1Name, process_,
std::unique_ptr<base::DictionaryValue>()));
listeners_->RemoveListenersForProcess(process_); listeners_->RemoveListenersForProcess(process_);
ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name)); ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
} }
...@@ -351,23 +462,41 @@ TEST_F(EventListenerMapTest, HasListenerForEventForURL) { ...@@ -351,23 +462,41 @@ TEST_F(EventListenerMapTest, HasListenerForEventForURL) {
base::BindRepeating(&CreateEventListenerForURL, GURL(kURL))); base::BindRepeating(&CreateEventListenerForURL, GURL(kURL)));
} }
TEST_F(EventListenerMapTest, HasListenerForEventForExtensionServiceWorker) {
TestHasListenerForEvent(base::BindRepeating(
&CreateEventListenerForExtensionServiceWorker, kExt1Id));
}
TEST_F(EventListenerMapTest, HasListenerForExtension) { TEST_F(EventListenerMapTest, HasListenerForExtension) {
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name)); ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
// Non-lazy listener. auto create_event_listener = [&](bool is_for_service_worker, bool lazy) {
listeners_->AddListener(EventListener::ForExtension( auto filter = std::unique_ptr<base::DictionaryValue>();
kEvent1Name, kExt1Id, process_, std::unique_ptr<DictionaryValue>())); if (is_for_service_worker) {
// Lazy listener. return EventListener::ForExtensionServiceWorker(
listeners_->AddListener(EventListener::ForExtension( kEvent1Name, kExt1Id, lazy ? nullptr : process_,
kEvent1Name, kExt1Id, nullptr, std::unique_ptr<DictionaryValue>())); Extension::GetBaseURLFromExtensionId(kExt1Id),
GetWorkerVersionId(lazy), GetWorkerThreadId(lazy), std::move(filter));
}
return EventListener::ForExtension(
kEvent1Name, kExt1Id, lazy ? nullptr : process_, std::move(filter));
};
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent2Name)); for (bool is_for_service_worker : {false, true}) {
ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name)); // Non-lazy listener.
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt2Id, kEvent1Name)); listeners_->AddListener(
listeners_->RemoveListenersForProcess(process_); create_event_listener(is_for_service_worker, false));
ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name)); // Lazy listener.
listeners_->RemoveListenersForExtension(kExt1Id); listeners_->AddListener(create_event_listener(is_for_service_worker, true));
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent2Name));
ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt2Id, kEvent1Name));
listeners_->RemoveListenersForProcess(process_);
ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
listeners_->RemoveListenersForExtension(kExt1Id);
ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
}
} }
TEST_F(EventListenerMapTest, AddLazyListenersFromPreferences) { TEST_F(EventListenerMapTest, AddLazyListenersFromPreferences) {
...@@ -398,10 +527,11 @@ TEST_F(EventListenerMapTest, AddLazyListenersFromPreferences) { ...@@ -398,10 +527,11 @@ TEST_F(EventListenerMapTest, AddLazyListenersFromPreferences) {
std::set<const EventListener*> targets( std::set<const EventListener*> targets(
listeners_->GetEventListeners(*event)); listeners_->GetEventListeners(*event));
ASSERT_EQ(1u, targets.size()); ASSERT_EQ(1u, targets.size());
EXPECT_TRUE(CreateLazyListener(kEvent1Name, kExt1Id, EXPECT_TRUE(
test_case.filter_host_suffix, CreateLazyListener(kEvent1Name, kExt1Id,
is_for_service_worker) CreateHostSuffixFilter(test_case.filter_host_suffix),
->Equals(*targets.begin())); is_for_service_worker)
->Equals(*targets.begin()));
} }
// Non-matching filter. // Non-matching filter.
...@@ -420,6 +550,7 @@ TEST_F(EventListenerMapTest, CorruptedExtensionPrefsShouldntCrash) { ...@@ -420,6 +550,7 @@ TEST_F(EventListenerMapTest, CorruptedExtensionPrefsShouldntCrash) {
filtered_listeners.Set(kEvent1Name, CreateHostSuffixFilter("google.com")); filtered_listeners.Set(kEvent1Name, CreateHostSuffixFilter("google.com"));
listeners_->LoadFilteredLazyListeners(kExt1Id, false, filtered_listeners); listeners_->LoadFilteredLazyListeners(kExt1Id, false, filtered_listeners);
listeners_->LoadFilteredLazyListeners(kExt1Id, true, filtered_listeners);
std::unique_ptr<Event> event( std::unique_ptr<Event> event(
CreateEvent(kEvent1Name, GURL("http://www.google.com"))); CreateEvent(kEvent1Name, GURL("http://www.google.com")));
...@@ -427,6 +558,13 @@ TEST_F(EventListenerMapTest, CorruptedExtensionPrefsShouldntCrash) { ...@@ -427,6 +558,13 @@ TEST_F(EventListenerMapTest, CorruptedExtensionPrefsShouldntCrash) {
ASSERT_EQ(0u, targets.size()); ASSERT_EQ(0u, targets.size());
} }
INSTANTIATE_TEST_SUITE_P(NonServiceWorker,
EventListenerMapWithContextTest,
testing::Values(false));
INSTANTIATE_TEST_SUITE_P(ServiceWorker,
EventListenerMapWithContextTest,
testing::Values(true));
} // namespace } // namespace
} // namespace extensions } // namespace extensions
...@@ -91,6 +91,19 @@ std::unique_ptr<EventListener> CreateEventListenerForURL( ...@@ -91,6 +91,19 @@ std::unique_ptr<EventListener> CreateEventListenerForURL(
std::move(filter)); std::move(filter));
} }
std::unique_ptr<EventListener> CreateEventListenerForExtensionServiceWorker(
const std::string& extension_id,
int64_t service_worker_version_id,
int worker_thread_id,
const std::string& event_name,
content::RenderProcessHost* process,
std::unique_ptr<base::DictionaryValue> filter) {
return EventListener::ForExtensionServiceWorker(
event_name, extension_id, process,
Extension::GetBaseURLFromExtensionId(extension_id),
service_worker_version_id, worker_thread_id, std::move(filter));
}
// Creates an extension. If |component| is true, it is created as a component // Creates an extension. If |component| is true, it is created as a component
// extension. If |persistent| is true, it is created with a persistent // extension. If |persistent| is true, it is created with a persistent
// background page; otherwise it is created with an event page. // background page; otherwise it is created with an event page.
...@@ -314,6 +327,13 @@ TEST_F(EventRouterTest, EventRouterObserverForURLs) { ...@@ -314,6 +327,13 @@ TEST_F(EventRouterTest, EventRouterObserverForURLs) {
&CreateEventListenerForURL, GURL("http://google.com/path"))); &CreateEventListenerForURL, GURL("http://google.com/path")));
} }
TEST_F(EventRouterTest, EventRouterObserverForServiceWorkers) {
RunEventRouterObserverTest(base::BindRepeating(
&CreateEventListenerForExtensionServiceWorker, "extension_id",
// Dummy version_id and thread_id.
99, 199));
}
TEST_F(EventRouterTest, TestReportEvent) { TEST_F(EventRouterTest, TestReportEvent) {
EventRouter router(browser_context(), nullptr); EventRouter router(browser_context(), nullptr);
scoped_refptr<const Extension> normal = ExtensionBuilder("Test").Build(); scoped_refptr<const Extension> normal = ExtensionBuilder("Test").Build();
......
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