Commit 19be6547 authored by Michael Thiessen's avatar Michael Thiessen Committed by Commit Bot

Reland "Decouple Scheme Registration from ContentMain"

Reason for revert: Broke Linux MSan Tests. See https://ci.chromium.org/p/chromium/builders/ci/Linux%20MSan%20Tests/21213 for first failing build.
Reland fixes uninitialized variable in shell/common/shell_content_client.h

Original change's description:
> Decouple Scheme Registration from ContentMain
>
> Split off of
> https://chromium-review.googlesource.com/c/chromium/src/+/1901591, see
> that CL (and the bug) for additional context.
>
> The followup to this CL locks the SchemeRegistry on first use to ensure
> schemes aren't added after GURL is used (which could lead to bugs). This
> means that Scheme initialization must happen before ContentMain in order
> for Java to use GURL before running ContentMain (I set the ContentClient
> in JNI_Onload, and register schemes after the CommandLine is initialized
> in content library_loader_hooks).
>
> Because of that, ContentMainDelegate had to have a ContentClient
> available earlier, so I added an API to ContentMainDelegate for the
> embedder to create their ContentClient on-demand.
>
> Because I wanted to avoid registering schemes multiple times,
> url_schemes.cc had to be made stateful and remember whether schemes had
> already been registered (Schemes could be registered by ContentMain
> after JNI_Onload already registered them for Android).
>
> Doc: https://docs.google.com/document/d/1kDKqBaq-b6EbUm0F4ea7ARoksUcj1KUx3qxFuSXEwM4/edit
>
> Bug: 783819
> Change-Id: I0f6884b2def87dcca77e722cd3d3800017c18749
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1945926
> Reviewed-by: John Abd-El-Malek <jam@chromium.org>
> Reviewed-by: Albert J. Wong <ajwong@chromium.org>
> Commit-Queue: Michael Thiessen <mthiesse@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#725799}

TBR=ajwong@chromium.org,mthiesse@chromium.org,jam@chromium.org

Change-Id: I27a2a49dc23610350ec9f3f96ed9824e2eeed0d5
Bug: 783819
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1973198
Commit-Queue: Michael Thiessen <mthiesse@chromium.org>
Reviewed-by: default avatarMichael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/master@{#725981}
parent 3e61bbf8
...@@ -75,8 +75,6 @@ AwMainDelegate::AwMainDelegate() = default; ...@@ -75,8 +75,6 @@ AwMainDelegate::AwMainDelegate() = default;
AwMainDelegate::~AwMainDelegate() = default; AwMainDelegate::~AwMainDelegate() = default;
bool AwMainDelegate::BasicStartupComplete(int* exit_code) { bool AwMainDelegate::BasicStartupComplete(int* exit_code) {
content::SetContentClient(&content_client_);
base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
// WebView uses the Android system's scrollbars and overscroll glow. // WebView uses the Android system's scrollbars and overscroll glow.
...@@ -349,6 +347,10 @@ void AwMainDelegate::PostFieldTrialInitialization() { ...@@ -349,6 +347,10 @@ void AwMainDelegate::PostFieldTrialInitialization() {
#endif #endif
} }
content::ContentClient* AwMainDelegate::CreateContentClient() {
return &content_client_;
}
content::ContentBrowserClient* AwMainDelegate::CreateContentBrowserClient() { content::ContentBrowserClient* AwMainDelegate::CreateContentBrowserClient() {
DCHECK(!aw_feature_list_creator_); DCHECK(!aw_feature_list_creator_);
aw_feature_list_creator_ = std::make_unique<AwFeatureListCreator>(); aw_feature_list_creator_ = std::make_unique<AwFeatureListCreator>();
......
...@@ -47,6 +47,7 @@ class AwMainDelegate : public content::ContentMainDelegate { ...@@ -47,6 +47,7 @@ class AwMainDelegate : public content::ContentMainDelegate {
bool ShouldCreateFeatureList() override; bool ShouldCreateFeatureList() override;
void PostEarlyInitialization(bool is_running_tests) override; void PostEarlyInitialization(bool is_running_tests) override;
void PostFieldTrialInitialization() override; void PostFieldTrialInitialization() override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentGpuClient* CreateContentGpuClient() override; content::ContentGpuClient* CreateContentGpuClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
......
...@@ -21,16 +21,15 @@ ShellMainDelegate::ShellMainDelegate() = default; ...@@ -21,16 +21,15 @@ ShellMainDelegate::ShellMainDelegate() = default;
ShellMainDelegate::~ShellMainDelegate() = default; ShellMainDelegate::~ShellMainDelegate() = default;
bool ShellMainDelegate::BasicStartupComplete(int* exit_code) {
content::SetContentClient(&content_client_);
return false;
}
void ShellMainDelegate::PreSandboxStartup() { void ShellMainDelegate::PreSandboxStartup() {
InitializeResourceBundle(); InitializeResourceBundle();
ui::InitializeInputMethodForTesting(); ui::InitializeInputMethodForTesting();
} }
content::ContentClient* ShellMainDelegate::CreateContentClient() {
return &content_client_;
}
content::ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() { content::ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() {
browser_client_.reset(new ShellContentBrowserClient); browser_client_.reset(new ShellContentBrowserClient);
return browser_client_.get(); return browser_client_.get();
......
...@@ -22,8 +22,8 @@ class ShellMainDelegate : public content::ContentMainDelegate { ...@@ -22,8 +22,8 @@ class ShellMainDelegate : public content::ContentMainDelegate {
ShellMainDelegate(); ShellMainDelegate();
~ShellMainDelegate() override; ~ShellMainDelegate() override;
bool BasicStartupComplete(int* exit_code) override;
void PreSandboxStartup() override; void PreSandboxStartup() override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
private: private:
......
...@@ -810,8 +810,6 @@ bool ChromeMainDelegate::BasicStartupComplete(int* exit_code) { ...@@ -810,8 +810,6 @@ bool ChromeMainDelegate::BasicStartupComplete(int* exit_code) {
} }
#endif #endif
content::SetContentClient(&chrome_content_client_);
// The TLS slot used by the memlog allocator shim needs to be initialized // The TLS slot used by the memlog allocator shim needs to be initialized
// early to ensure that it gets assigned a low slot number. If it gets // early to ensure that it gets assigned a low slot number. If it gets
// initialized too late, the glibc TLS system will require a malloc call in // initialized too late, the glibc TLS system will require a malloc call in
...@@ -1191,6 +1189,10 @@ void ChromeMainDelegate::ZygoteForked() { ...@@ -1191,6 +1189,10 @@ void ChromeMainDelegate::ZygoteForked() {
#endif // defined(OS_LINUX) #endif // defined(OS_LINUX)
content::ContentClient* ChromeMainDelegate::CreateContentClient() {
return &chrome_content_client_;
}
content::ContentBrowserClient* content::ContentBrowserClient*
ChromeMainDelegate::CreateContentBrowserClient() { ChromeMainDelegate::CreateContentBrowserClient() {
#if defined(CHROME_MULTIPLE_DLL_CHILD) #if defined(CHROME_MULTIPLE_DLL_CHILD)
......
...@@ -69,6 +69,7 @@ class ChromeMainDelegate : public content::ContentMainDelegate { ...@@ -69,6 +69,7 @@ class ChromeMainDelegate : public content::ContentMainDelegate {
#endif // !defined(CHROME_MULTIPLE_DLL_CHILD) #endif // !defined(CHROME_MULTIPLE_DLL_CHILD)
void PostFieldTrialInitialization() override; void PostFieldTrialInitialization() override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentGpuClient* CreateContentGpuClient() override; content::ContentGpuClient* CreateContentGpuClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
......
...@@ -131,8 +131,6 @@ bool CastMainDelegate::BasicStartupComplete(int* exit_code) { ...@@ -131,8 +131,6 @@ bool CastMainDelegate::BasicStartupComplete(int* exit_code) {
} }
} }
#endif // defined(OS_ANDROID) #endif // defined(OS_ANDROID)
content::SetContentClient(&content_client_);
return false; return false;
} }
...@@ -282,6 +280,10 @@ void CastMainDelegate::InitializeResourceBundle() { ...@@ -282,6 +280,10 @@ void CastMainDelegate::InitializeResourceBundle() {
#endif // defined(OS_ANDROID) #endif // defined(OS_ANDROID)
} }
content::ContentClient* CastMainDelegate::CreateContentClient() {
return &content_client_;
}
content::ContentBrowserClient* CastMainDelegate::CreateContentBrowserClient() { content::ContentBrowserClient* CastMainDelegate::CreateContentBrowserClient() {
DCHECK(!cast_feature_list_creator_); DCHECK(!cast_feature_list_creator_);
cast_feature_list_creator_ = std::make_unique<CastFeatureListCreator>(); cast_feature_list_creator_ = std::make_unique<CastFeatureListCreator>();
......
...@@ -48,6 +48,7 @@ class CastMainDelegate : public content::ContentMainDelegate { ...@@ -48,6 +48,7 @@ class CastMainDelegate : public content::ContentMainDelegate {
#endif // defined(OS_LINUX) #endif // defined(OS_LINUX)
bool ShouldCreateFeatureList() override; bool ShouldCreateFeatureList() override;
void PostEarlyInitialization(bool is_running_tests) override; void PostEarlyInitialization(bool is_running_tests) override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentGpuClient* CreateContentGpuClient() override; content::ContentGpuClient* CreateContentGpuClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "content/public/android/content_jni_headers/ContentMain_jni.h" #include "content/public/android/content_jni_headers/ContentMain_jni.h"
#include "content/public/app/content_main.h" #include "content/public/app/content_main.h"
#include "content/public/app/content_main_delegate.h" #include "content/public/app/content_main_delegate.h"
#include "content/public/common/content_client.h"
#include "services/service_manager/embedder/main.h" #include "services/service_manager/embedder/main.h"
using base::LazyInstance; using base::LazyInstance;
...@@ -27,6 +28,15 @@ LazyInstance<std::unique_ptr<ContentMainDelegate>>::DestructorAtExit ...@@ -27,6 +28,15 @@ LazyInstance<std::unique_ptr<ContentMainDelegate>>::DestructorAtExit
} // namespace } // namespace
class ContentClientCreator {
public:
static void Create(ContentMainDelegate* delegate) {
ContentClient* client = delegate->CreateContentClient();
DCHECK(client);
SetContentClient(client);
}
};
// TODO(qinmin/hanxi): split this function into 2 separate methods: One to // TODO(qinmin/hanxi): split this function into 2 separate methods: One to
// start the ServiceManager and one to start the remainder of the browser // start the ServiceManager and one to start the remainder of the browser
// process. The first method should always be called upon browser start, and // process. The first method should always be called upon browser start, and
...@@ -55,6 +65,10 @@ static jint JNI_ContentMain_Start(JNIEnv* env, ...@@ -55,6 +65,10 @@ static jint JNI_ContentMain_Start(JNIEnv* env,
void SetContentMainDelegate(ContentMainDelegate* delegate) { void SetContentMainDelegate(ContentMainDelegate* delegate) {
DCHECK(!g_content_main_delegate.Get().get()); DCHECK(!g_content_main_delegate.Get().get());
g_content_main_delegate.Get().reset(delegate); g_content_main_delegate.Get().reset(delegate);
// The ContentClient needs to be set early so that it can be used by the
// content library loader hooks.
if (!GetContentClient())
ContentClientCreator::Create(delegate);
} }
ContentMainDelegate* GetContentMainDelegateForTesting() { ContentMainDelegate* GetContentMainDelegateForTesting() {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "content/common/content_constants_internal.h" #include "content/common/content_constants_internal.h"
#include "content/common/url_schemes.h"
#include "services/tracing/public/cpp/trace_startup.h" #include "services/tracing/public/cpp/trace_startup.h"
namespace content { namespace content {
...@@ -54,6 +55,10 @@ bool LibraryLoaded(JNIEnv* env, ...@@ -54,6 +55,10 @@ bool LibraryLoaded(JNIEnv* env,
<< ", default verbosity = " << logging::GetVlogVerbosity(); << ", default verbosity = " << logging::GetVlogVerbosity();
} }
// Content Schemes need to be registered as early as possible after the
// CommandLine has been initialized to allow java and tests to use GURL before
// running ContentMain.
RegisterContentSchemes();
return true; return true;
} }
......
...@@ -399,6 +399,15 @@ void PreSandboxInit() { ...@@ -399,6 +399,15 @@ void PreSandboxInit() {
} // namespace } // namespace
class ContentClientCreator {
public:
static void Create(ContentMainDelegate* delegate) {
ContentClient* client = delegate->CreateContentClient();
DCHECK(client);
SetContentClient(client);
}
};
class ContentClientInitializer { class ContentClientInitializer {
public: public:
static void Set(const std::string& process_type, static void Set(const std::string& process_type,
...@@ -643,6 +652,8 @@ int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) { ...@@ -643,6 +652,8 @@ int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) {
#endif // !OS_ANDROID #endif // !OS_ANDROID
int exit_code = 0; int exit_code = 0;
if (!GetContentClient())
ContentClientCreator::Create(delegate_);
if (delegate_->BasicStartupComplete(&exit_code)) if (delegate_->BasicStartupComplete(&exit_code))
return exit_code; return exit_code;
completed_basic_startup_ = true; completed_basic_startup_ = true;
...@@ -662,8 +673,7 @@ int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) { ...@@ -662,8 +673,7 @@ int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) {
} }
#endif #endif
if (!GetContentClient()) RegisterContentSchemes();
SetContentClient(&empty_content_client_);
ContentClientInitializer::Set(process_type, delegate_); ContentClientInitializer::Set(process_type, delegate_);
#if !defined(OS_ANDROID) #if !defined(OS_ANDROID)
...@@ -724,7 +734,6 @@ int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) { ...@@ -724,7 +734,6 @@ int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) {
#endif #endif
RegisterPathProvider(); RegisterPathProvider();
RegisterContentSchemes(delegate_->ShouldLockSchemeRegistry());
#if defined(OS_ANDROID) && (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE) #if defined(OS_ANDROID) && (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
// On Android, we have two ICU data files. A main one with most languages // On Android, we have two ICU data files. A main one with most languages
......
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
#include "content/browser/startup_data_impl.h" #include "content/browser/startup_data_impl.h"
#include "content/public/app/content_main.h" #include "content/public/app/content_main.h"
#include "content/public/app/content_main_runner.h" #include "content/public/app/content_main_runner.h"
#include "content/public/common/content_client.h"
#include "content/public/common/main_function_params.h" #include "content/public/common/main_function_params.h"
#include "mojo/core/embedder/scoped_ipc_support.h" #include "mojo/core/embedder/scoped_ipc_support.h"
...@@ -73,9 +72,6 @@ class ContentMainRunnerImpl : public ContentMainRunner { ...@@ -73,9 +72,6 @@ class ContentMainRunnerImpl : public ContentMainRunner {
// True if basic startup was completed. // True if basic startup was completed.
bool completed_basic_startup_ = false; bool completed_basic_startup_ = false;
// Used if the embedder doesn't set one.
ContentClient empty_content_client_;
// The delegate will outlive this object. // The delegate will outlive this object.
ContentMainDelegate* delegate_ = nullptr; ContentMainDelegate* delegate_ = nullptr;
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
namespace content { namespace content {
namespace { namespace {
bool g_registered_url_schemes = false;
const char* const kDefaultSavableSchemes[] = { const char* const kDefaultSavableSchemes[] = {
url::kHttpScheme, url::kHttpScheme,
url::kHttpsScheme, url::kHttpsScheme,
...@@ -47,7 +49,11 @@ std::vector<std::string>& GetMutableServiceWorkerSchemes() { ...@@ -47,7 +49,11 @@ std::vector<std::string>& GetMutableServiceWorkerSchemes() {
} // namespace } // namespace
void RegisterContentSchemes(bool lock_schemes) { void RegisterContentSchemes() {
// On Android, schemes may have been registered already.
if (g_registered_url_schemes)
return;
g_registered_url_schemes = true;
ContentClient::Schemes schemes; ContentClient::Schemes schemes;
GetContentClient()->AddAdditionalSchemes(&schemes); GetContentClient()->AddAdditionalSchemes(&schemes);
...@@ -91,14 +97,6 @@ void RegisterContentSchemes(bool lock_schemes) { ...@@ -91,14 +97,6 @@ void RegisterContentSchemes(bool lock_schemes) {
url::EnableNonStandardSchemesForAndroidWebView(); url::EnableNonStandardSchemesForAndroidWebView();
#endif #endif
// Prevent future modification of the scheme lists. This is to prevent
// accidental creation of data races in the program. Add*Scheme aren't
// threadsafe so must be called when GURL isn't used on any other thread. This
// is really easy to mess up, so we say that all calls to Add*Scheme in Chrome
// must be inside this function.
if (lock_schemes)
url::LockSchemeRegistries();
// Combine the default savable schemes with the additional ones given. // Combine the default savable schemes with the additional ones given.
GetMutableSavableSchemes().assign(std::begin(kDefaultSavableSchemes), GetMutableSavableSchemes().assign(std::begin(kDefaultSavableSchemes),
std::end(kDefaultSavableSchemes)); std::end(kDefaultSavableSchemes));
...@@ -109,6 +107,11 @@ void RegisterContentSchemes(bool lock_schemes) { ...@@ -109,6 +107,11 @@ void RegisterContentSchemes(bool lock_schemes) {
GetMutableServiceWorkerSchemes() = std::move(schemes.service_worker_schemes); GetMutableServiceWorkerSchemes() = std::move(schemes.service_worker_schemes);
} }
void ReRegisterContentSchemesForTests() {
g_registered_url_schemes = false;
RegisterContentSchemes();
}
const std::vector<std::string>& GetSavableSchemes() { const std::vector<std::string>& GetSavableSchemes() {
return GetMutableSavableSchemes(); return GetMutableSavableSchemes();
} }
......
...@@ -12,17 +12,13 @@ ...@@ -12,17 +12,13 @@
namespace content { namespace content {
// Note: ContentMainRunner calls this method internally as part of main
// initialization, so this function generally should not be called by
// embedders. It's exported to facilitate test harnesses that do not
// utilize ContentMainRunner and that do not wish to lock the set
// of standard schemes at init time.
//
// Called near the beginning of startup to register URL schemes that should be // Called near the beginning of startup to register URL schemes that should be
// parsed as "standard" or "referrer" with the src/url/ library. Optionally, the // parsed as "standard" or "referrer" with the src/url/ library. The embedder
// sets of schemes are locked down. The embedder can add additional schemes by // can add additional schemes by overriding the AddAdditionalSchemes method.
// overriding the ContentClient::AddAdditionalSchemes method. CONTENT_EXPORT void RegisterContentSchemes();
CONTENT_EXPORT void RegisterContentSchemes(bool lock_schemes);
// Re-initializes schemes for tests.
CONTENT_EXPORT void ReRegisterContentSchemesForTests();
// See comment in ContentClient::AddAdditionalSchemes for explanations. These // See comment in ContentClient::AddAdditionalSchemes for explanations. These
// getters can be invoked on any thread. // getters can be invoked on any thread.
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/public/common/content_client.h"
#include "content/public/gpu/content_gpu_client.h" #include "content/public/gpu/content_gpu_client.h"
#include "content/public/renderer/content_renderer_client.h" #include "content/public/renderer/content_renderer_client.h"
#include "content/public/utility/content_utility_client.h" #include "content/public/utility/content_utility_client.h"
...@@ -51,10 +52,6 @@ int ContentMainDelegate::TerminateForFatalInitializationError() { ...@@ -51,10 +52,6 @@ int ContentMainDelegate::TerminateForFatalInitializationError() {
return 0; return 0;
} }
bool ContentMainDelegate::ShouldLockSchemeRegistry() {
return true;
}
service_manager::ProcessType ContentMainDelegate::OverrideProcessType() { service_manager::ProcessType ContentMainDelegate::OverrideProcessType() {
return service_manager::ProcessType::kDefault; return service_manager::ProcessType::kDefault;
} }
...@@ -71,6 +68,10 @@ bool ContentMainDelegate::ShouldCreateFeatureList() { ...@@ -71,6 +68,10 @@ bool ContentMainDelegate::ShouldCreateFeatureList() {
return true; return true;
} }
ContentClient* ContentMainDelegate::CreateContentClient() {
return new ContentClient();
}
ContentBrowserClient* ContentMainDelegate::CreateContentBrowserClient() { ContentBrowserClient* ContentMainDelegate::CreateContentBrowserClient() {
#if defined(CHROME_MULTIPLE_DLL_CHILD) #if defined(CHROME_MULTIPLE_DLL_CHILD)
return NULL; return NULL;
......
...@@ -27,6 +27,7 @@ class ZygoteForkDelegate; ...@@ -27,6 +27,7 @@ class ZygoteForkDelegate;
namespace content { namespace content {
class ContentBrowserClient; class ContentBrowserClient;
class ContentClient;
class ContentGpuClient; class ContentGpuClient;
class ContentRendererClient; class ContentRendererClient;
class ContentUtilityClient; class ContentUtilityClient;
...@@ -83,20 +84,6 @@ class CONTENT_EXPORT ContentMainDelegate { ...@@ -83,20 +84,6 @@ class CONTENT_EXPORT ContentMainDelegate {
virtual void ZygoteForked() {} virtual void ZygoteForked() {}
#endif // defined(OS_LINUX) #endif // defined(OS_LINUX)
// Allows the embedder to prevent locking the scheme registry. The scheme
// registry is the list of URL schemes we recognize, with some additional
// information about each scheme such as whether it expects a host. The
// scheme registry is not thread-safe, so by default it is locked before any
// threads are created to ensure single-threaded access. An embedder can
// override this to prevent the scheme registry from being locked during
// startup, but if they do so then they are responsible for making sure that
// the registry is only accessed in a thread-safe way, and for calling
// url::LockSchemeRegistries() when initialization is complete. If possible,
// prefer registering additional schemes through
// ContentClient::AddAdditionalSchemes over preventing the scheme registry
// from being locked.
virtual bool ShouldLockSchemeRegistry();
// Fatal errors during initialization are reported by this function, so that // Fatal errors during initialization are reported by this function, so that
// the embedder can implement graceful exit by displaying some message and // the embedder can implement graceful exit by displaying some message and
// returning initialization error code. Default behavior is CHECK(false). // returning initialization error code. Default behavior is CHECK(false).
...@@ -150,12 +137,14 @@ class CONTENT_EXPORT ContentMainDelegate { ...@@ -150,12 +137,14 @@ class CONTENT_EXPORT ContentMainDelegate {
virtual void PostEarlyInitialization(bool is_running_tests) {} virtual void PostEarlyInitialization(bool is_running_tests) {}
protected: protected:
friend class ContentClientCreator;
friend class ContentClientInitializer; friend class ContentClientInitializer;
friend class BrowserTestBase; friend class BrowserTestBase;
// Called once per relevant process type to allow the embedder to customize // Called once per relevant process type to allow the embedder to customize
// content. If an embedder wants the default (empty) implementation, don't // content. If an embedder wants the default (empty) implementation, don't
// override this. // override this.
virtual ContentClient* CreateContentClient();
virtual ContentBrowserClient* CreateContentBrowserClient(); virtual ContentBrowserClient* CreateContentBrowserClient();
virtual ContentGpuClient* CreateContentGpuClient(); virtual ContentGpuClient* CreateContentGpuClient();
virtual ContentRendererClient* CreateContentRendererClient(); virtual ContentRendererClient* CreateContentRendererClient();
......
...@@ -408,7 +408,7 @@ void BrowserTestBase::SetUp() { ...@@ -408,7 +408,7 @@ void BrowserTestBase::SetUp() {
SetRendererClientForTesting(delegate->CreateContentRendererClient()); SetRendererClientForTesting(delegate->CreateContentRendererClient());
content::RegisterPathProvider(); content::RegisterPathProvider();
content::RegisterContentSchemes(false); content::RegisterContentSchemes();
ui::RegisterPathProvider(); ui::RegisterPathProvider();
delegate->PreSandboxStartup(); delegate->PreSandboxStartup();
......
...@@ -77,7 +77,7 @@ void ContentTestSuiteBase::Initialize() { ...@@ -77,7 +77,7 @@ void ContentTestSuiteBase::Initialize() {
void ContentTestSuiteBase::RegisterContentSchemes( void ContentTestSuiteBase::RegisterContentSchemes(
ContentClient* content_client) { ContentClient* content_client) {
SetContentClient(content_client); SetContentClient(content_client);
content::RegisterContentSchemes(false); content::RegisterContentSchemes();
SetContentClient(nullptr); SetContentClient(nullptr);
} }
......
...@@ -200,7 +200,7 @@ void IsolateAllSitesForTesting(base::CommandLine* command_line) { ...@@ -200,7 +200,7 @@ void IsolateAllSitesForTesting(base::CommandLine* command_line) {
void ResetSchemesAndOriginsWhitelist() { void ResetSchemesAndOriginsWhitelist() {
url::ResetForTests(); url::ResetForTests();
RegisterContentSchemes(false); ReRegisterContentSchemesForTests();
} }
GURL GetWebUIURL(const std::string& host) { GURL GetWebUIURL(const std::string& host) {
......
...@@ -216,8 +216,6 @@ jumbo_static_library("content_shell_lib") { ...@@ -216,8 +216,6 @@ jumbo_static_library("content_shell_lib") {
"common/shell_origin_trial_policy.h", "common/shell_origin_trial_policy.h",
"common/shell_switches.cc", "common/shell_switches.cc",
"common/shell_switches.h", "common/shell_switches.h",
"common/web_test/web_test_content_client.cc",
"common/web_test/web_test_content_client.h",
"gpu/shell_content_gpu_client.cc", "gpu/shell_content_gpu_client.cc",
"gpu/shell_content_gpu_client.h", "gpu/shell_content_gpu_client.h",
"renderer/shell_content_renderer_client.cc", "renderer/shell_content_renderer_client.cc",
......
...@@ -32,7 +32,6 @@ ...@@ -32,7 +32,6 @@
#include "content/shell/browser/web_test/web_test_content_browser_client.h" #include "content/shell/browser/web_test/web_test_content_browser_client.h"
#include "content/shell/common/shell_content_client.h" #include "content/shell/common/shell_content_client.h"
#include "content/shell/common/shell_switches.h" #include "content/shell/common/shell_switches.h"
#include "content/shell/common/web_test/web_test_content_client.h"
#include "content/shell/common/web_test/web_test_switches.h" #include "content/shell/common/web_test/web_test_switches.h"
#include "content/shell/gpu/shell_content_gpu_client.h" #include "content/shell/gpu/shell_content_gpu_client.h"
#include "content/shell/renderer/shell_content_renderer_client.h" #include "content/shell/renderer/shell_content_renderer_client.h"
...@@ -304,11 +303,8 @@ bool ShellMainDelegate::BasicStartupComplete(int* exit_code) { ...@@ -304,11 +303,8 @@ bool ShellMainDelegate::BasicStartupComplete(int* exit_code) {
#endif #endif
} }
content_client_.reset(switches::IsRunWebTestsSwitchPresent() if (switches::IsRunWebTestsSwitchPresent())
? new WebTestContentClient content_client_->SetInWebTest(true);
: new ShellContentClient);
SetContentClient(content_client_.get());
return false; return false;
} }
...@@ -454,6 +450,11 @@ void ShellMainDelegate::PreCreateMainMessageLoop() { ...@@ -454,6 +450,11 @@ void ShellMainDelegate::PreCreateMainMessageLoop() {
#endif #endif
} }
ContentClient* ShellMainDelegate::CreateContentClient() {
content_client_ = std::make_unique<ShellContentClient>();
return content_client_.get();
}
ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() { ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() {
browser_client_.reset(switches::IsRunWebTestsSwitchPresent() browser_client_.reset(switches::IsRunWebTestsSwitchPresent()
? new WebTestContentBrowserClient ? new WebTestContentBrowserClient
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#include "content/public/app/content_main_delegate.h" #include "content/public/app/content_main_delegate.h"
namespace content { namespace content {
class ContentClient; class ShellContentClient;
class ShellContentBrowserClient; class ShellContentBrowserClient;
class ShellContentGpuClient; class ShellContentGpuClient;
class ShellContentRendererClient; class ShellContentRendererClient;
...@@ -33,6 +33,7 @@ class ShellMainDelegate : public ContentMainDelegate { ...@@ -33,6 +33,7 @@ class ShellMainDelegate : public ContentMainDelegate {
void ZygoteForked() override; void ZygoteForked() override;
#endif #endif
void PreCreateMainMessageLoop() override; void PreCreateMainMessageLoop() override;
ContentClient* CreateContentClient() override;
ContentBrowserClient* CreateContentBrowserClient() override; ContentBrowserClient* CreateContentBrowserClient() override;
ContentGpuClient* CreateContentGpuClient() override; ContentGpuClient* CreateContentGpuClient() override;
ContentRendererClient* CreateContentRendererClient() override; ContentRendererClient* CreateContentRendererClient() override;
...@@ -46,7 +47,7 @@ class ShellMainDelegate : public ContentMainDelegate { ...@@ -46,7 +47,7 @@ class ShellMainDelegate : public ContentMainDelegate {
std::unique_ptr<ShellContentGpuClient> gpu_client_; std::unique_ptr<ShellContentGpuClient> gpu_client_;
std::unique_ptr<ShellContentRendererClient> renderer_client_; std::unique_ptr<ShellContentRendererClient> renderer_client_;
std::unique_ptr<ShellContentUtilityClient> utility_client_; std::unique_ptr<ShellContentUtilityClient> utility_client_;
std::unique_ptr<ContentClient> content_client_; std::unique_ptr<ShellContentClient> content_client_;
DISALLOW_COPY_AND_ASSIGN(ShellMainDelegate); DISALLOW_COPY_AND_ASSIGN(ShellMainDelegate);
}; };
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "content/app/resources/grit/content_resources.h" #include "content/app/resources/grit/content_resources.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "content/shell/common/shell_switches.h" #include "content/shell/common/shell_switches.h"
#include "content/shell/common/web_test/blink_test_messages.h"
#include "content/shell/grit/shell_resources.h" #include "content/shell/grit/shell_resources.h"
#include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/public/strings/grit/blink_strings.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
...@@ -78,4 +79,21 @@ blink::OriginTrialPolicy* ShellContentClient::GetOriginTrialPolicy() { ...@@ -78,4 +79,21 @@ blink::OriginTrialPolicy* ShellContentClient::GetOriginTrialPolicy() {
return &origin_trial_policy_; return &origin_trial_policy_;
} }
bool ShellContentClient::CanSendWhileSwappedOut(const IPC::Message* message) {
if (!in_web_test_)
return ContentClient::CanSendWhileSwappedOut(message);
switch (message->type()) {
// Used in web tests; handled in BlinkTestController.
case BlinkTestHostMsg_PrintMessage::ID:
return true;
default:
return false;
}
}
void ShellContentClient::SetInWebTest(bool in_web_test) {
in_web_test_ = in_web_test;
}
} // namespace content } // namespace content
...@@ -26,9 +26,13 @@ class ShellContentClient : public ContentClient { ...@@ -26,9 +26,13 @@ class ShellContentClient : public ContentClient {
gfx::Image& GetNativeImageNamed(int resource_id) override; gfx::Image& GetNativeImageNamed(int resource_id) override;
base::DictionaryValue GetNetLogConstants() override; base::DictionaryValue GetNetLogConstants() override;
blink::OriginTrialPolicy* GetOriginTrialPolicy() override; blink::OriginTrialPolicy* GetOriginTrialPolicy() override;
bool CanSendWhileSwappedOut(const IPC::Message* message) override;
void SetInWebTest(bool in_web_test);
private: private:
ShellOriginTrialPolicy origin_trial_policy_; ShellOriginTrialPolicy origin_trial_policy_;
bool in_web_test_ = false;
}; };
} // namespace content } // namespace content
......
// Copyright 2016 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 "content/shell/common/web_test/web_test_content_client.h"
#include "content/shell/common/web_test/blink_test_messages.h"
namespace content {
bool WebTestContentClient::CanSendWhileSwappedOut(const IPC::Message* message) {
switch (message->type()) {
// Used in web tests; handled in BlinkTestController.
case BlinkTestHostMsg_PrintMessage::ID:
return true;
default:
return false;
}
}
} // namespace content
// Copyright 2016 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 CONTENT_SHELL_COMMON_WEB_TEST_WEB_TEST_CONTENT_CLIENT_H_
#define CONTENT_SHELL_COMMON_WEB_TEST_WEB_TEST_CONTENT_CLIENT_H_
#include "base/macros.h"
#include "content/shell/common/shell_content_client.h"
namespace content {
class WebTestContentClient : public ShellContentClient {
public:
WebTestContentClient() {}
bool CanSendWhileSwappedOut(const IPC::Message* message) override;
private:
DISALLOW_COPY_AND_ASSIGN(WebTestContentClient);
};
} // namespace content
#endif // CONTENT_SHELL_COMMON_WEB_TEST_WEB_TEST_CONTENT_CLIENT_H_
...@@ -134,8 +134,6 @@ ShellMainDelegate::~ShellMainDelegate() { ...@@ -134,8 +134,6 @@ ShellMainDelegate::~ShellMainDelegate() {
bool ShellMainDelegate::BasicStartupComplete(int* exit_code) { bool ShellMainDelegate::BasicStartupComplete(int* exit_code) {
InitLogging(); InitLogging();
content_client_.reset(new ShellContentClient);
SetContentClient(content_client_.get());
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
chromeos::RegisterPathProvider(); chromeos::RegisterPathProvider();
...@@ -163,6 +161,11 @@ void ShellMainDelegate::PreSandboxStartup() { ...@@ -163,6 +161,11 @@ void ShellMainDelegate::PreSandboxStartup() {
GetResourcesPakFilePath()); GetResourcesPakFilePath());
} }
content::ContentClient* ShellMainDelegate::CreateContentClient() {
content_client_ = std::make_unique<ShellContentClient>();
return content_client_.get();
}
content::ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() { content::ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() {
browser_client_ = std::make_unique<ShellContentBrowserClient>( browser_client_ = std::make_unique<ShellContentBrowserClient>(
new DefaultShellBrowserMainDelegate); new DefaultShellBrowserMainDelegate);
......
...@@ -29,6 +29,7 @@ class ShellMainDelegate : public content::ContentMainDelegate { ...@@ -29,6 +29,7 @@ class ShellMainDelegate : public content::ContentMainDelegate {
// ContentMainDelegate implementation: // ContentMainDelegate implementation:
bool BasicStartupComplete(int* exit_code) override; bool BasicStartupComplete(int* exit_code) override;
void PreSandboxStartup() override; void PreSandboxStartup() override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
void ProcessExiting(const std::string& process_type) override; void ProcessExiting(const std::string& process_type) override;
......
...@@ -52,9 +52,6 @@ bool WebEngineMainDelegate::BasicStartupComplete(int* exit_code) { ...@@ -52,9 +52,6 @@ bool WebEngineMainDelegate::BasicStartupComplete(int* exit_code) {
*exit_code = 1; *exit_code = 1;
return true; return true;
} }
content_client_ = std::make_unique<WebEngineContentClient>();
SetContentClient(content_client_.get());
return false; return false;
} }
...@@ -71,6 +68,11 @@ int WebEngineMainDelegate::RunProcess( ...@@ -71,6 +68,11 @@ int WebEngineMainDelegate::RunProcess(
return WebEngineBrowserMain(main_function_params); return WebEngineBrowserMain(main_function_params);
} }
content::ContentClient* WebEngineMainDelegate::CreateContentClient() {
content_client_ = std::make_unique<WebEngineContentClient>();
return content_client_.get();
}
content::ContentBrowserClient* content::ContentBrowserClient*
WebEngineMainDelegate::CreateContentBrowserClient() { WebEngineMainDelegate::CreateContentBrowserClient() {
DCHECK(!browser_client_); DCHECK(!browser_client_);
......
...@@ -40,6 +40,7 @@ class WEB_ENGINE_EXPORT WebEngineMainDelegate ...@@ -40,6 +40,7 @@ class WEB_ENGINE_EXPORT WebEngineMainDelegate
int RunProcess( int RunProcess(
const std::string& process_type, const std::string& process_type,
const content::MainFunctionParams& main_function_params) override; const content::MainFunctionParams& main_function_params) override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#define HEADLESS_LIB_HEADLESS_CONTENT_CLIENT_H_ #define HEADLESS_LIB_HEADLESS_CONTENT_CLIENT_H_
#include "content/public/common/content_client.h" #include "content/public/common/content_client.h"
#include "headless/public/headless_browser.h"
namespace headless { namespace headless {
......
...@@ -229,8 +229,6 @@ bool HeadlessContentMainDelegate::BasicStartupComplete(int* exit_code) { ...@@ -229,8 +229,6 @@ bool HeadlessContentMainDelegate::BasicStartupComplete(int* exit_code) {
command_line->AppendSwitch(::switches::kAllowPreCommitInput); command_line->AppendSwitch(::switches::kAllowPreCommitInput);
content::Profiling::ProcessStarted(); content::Profiling::ProcessStarted();
SetContentClient(&content_client_);
return false; return false;
} }
...@@ -449,6 +447,10 @@ HeadlessBrowser::Options* HeadlessContentMainDelegate::options() { ...@@ -449,6 +447,10 @@ HeadlessBrowser::Options* HeadlessContentMainDelegate::options() {
return options_.get(); return options_.get();
} }
content::ContentClient* HeadlessContentMainDelegate::CreateContentClient() {
return &content_client_;
}
#if !defined(CHROME_MULTIPLE_DLL_CHILD) #if !defined(CHROME_MULTIPLE_DLL_CHILD)
content::ContentBrowserClient* content::ContentBrowserClient*
HeadlessContentMainDelegate::CreateContentBrowserClient() { HeadlessContentMainDelegate::CreateContentBrowserClient() {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "content/public/browser/content_browser_client.h" #include "content/public/browser/content_browser_client.h"
#include "content/public/renderer/content_renderer_client.h" #include "content/public/renderer/content_renderer_client.h"
#include "headless/lib/headless_content_client.h" #include "headless/lib/headless_content_client.h"
#include "headless/public/headless_browser.h"
#include "headless/public/headless_export.h" #include "headless/public/headless_export.h"
#if !defined(CHROME_MULTIPLE_DLL_CHILD) #if !defined(CHROME_MULTIPLE_DLL_CHILD)
...@@ -50,6 +51,7 @@ class HEADLESS_EXPORT HeadlessContentMainDelegate ...@@ -50,6 +51,7 @@ class HEADLESS_EXPORT HeadlessContentMainDelegate
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
void PreCreateMainMessageLoop() override; void PreCreateMainMessageLoop() override;
#endif #endif
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentUtilityClient* CreateContentUtilityClient() override; content::ContentUtilityClient* CreateContentUtilityClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "third_party/ocmock/OCMock/OCMock.h" #include "third_party/ocmock/OCMock/OCMock.h"
#include "ui/base/page_transition_types.h" #include "ui/base/page_transition_types.h"
#include "url/scheme_host_port.h" #include "url/scheme_host_port.h"
#include "url/url_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
......
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
#include "ios/web/common/user_agent.h" #include "ios/web/common/user_agent.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h" #include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "ui/base/layout.h" #include "ui/base/layout.h"
#include "url/url_util.h"
namespace base { namespace base {
class RefCountedMemory; class RefCountedMemory;
......
...@@ -47,8 +47,6 @@ bool ViewsContentMainDelegate::BasicStartupComplete(int* exit_code) { ...@@ -47,8 +47,6 @@ bool ViewsContentMainDelegate::BasicStartupComplete(int* exit_code) {
std::string process_type = std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType); command_line.GetSwitchValueASCII(switches::kProcessType);
content::SetContentClient(&content_client_);
logging::LoggingSettings settings; logging::LoggingSettings settings;
settings.logging_dest = settings.logging_dest =
logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR; logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
...@@ -86,6 +84,10 @@ void ViewsContentMainDelegate::PreCreateMainMessageLoop() { ...@@ -86,6 +84,10 @@ void ViewsContentMainDelegate::PreCreateMainMessageLoop() {
ViewsContentClientMainParts::PreCreateMainMessageLoop(); ViewsContentClientMainParts::PreCreateMainMessageLoop();
} }
content::ContentClient* ViewsContentMainDelegate::CreateContentClient() {
return &content_client_;
}
content::ContentBrowserClient* content::ContentBrowserClient*
ViewsContentMainDelegate::CreateContentBrowserClient() { ViewsContentMainDelegate::CreateContentBrowserClient() {
browser_client_ = browser_client_ =
......
...@@ -25,6 +25,7 @@ class ViewsContentMainDelegate : public content::ContentMainDelegate { ...@@ -25,6 +25,7 @@ class ViewsContentMainDelegate : public content::ContentMainDelegate {
bool BasicStartupComplete(int* exit_code) override; bool BasicStartupComplete(int* exit_code) override;
void PreSandboxStartup() override; void PreSandboxStartup() override;
void PreCreateMainMessageLoop() override; void PreCreateMainMessageLoop() override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
private: private:
......
...@@ -137,8 +137,6 @@ bool ContentMainDelegateImpl::BasicStartupComplete(int* exit_code) { ...@@ -137,8 +137,6 @@ bool ContentMainDelegateImpl::BasicStartupComplete(int* exit_code) {
InitLogging(&params_); InitLogging(&params_);
content_client_ = std::make_unique<ContentClientImpl>();
SetContentClient(content_client_.get());
RegisterPathProvider(); RegisterPathProvider();
return false; return false;
...@@ -292,6 +290,11 @@ void ContentMainDelegateImpl::InitializeResourceBundle() { ...@@ -292,6 +290,11 @@ void ContentMainDelegateImpl::InitializeResourceBundle() {
#endif #endif
} }
content::ContentClient* ContentMainDelegateImpl::CreateContentClient() {
content_client_ = std::make_unique<ContentClientImpl>();
return content_client_.get();
}
content::ContentBrowserClient* content::ContentBrowserClient*
ContentMainDelegateImpl::CreateContentBrowserClient() { ContentMainDelegateImpl::CreateContentBrowserClient() {
browser_client_ = std::make_unique<ContentBrowserClientImpl>(&params_); browser_client_ = std::make_unique<ContentBrowserClientImpl>(&params_);
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
#include "weblayer/public/main.h" #include "weblayer/public/main.h"
namespace weblayer { namespace weblayer {
class ContentClientImpl;
class ContentBrowserClientImpl; class ContentBrowserClientImpl;
class ContentClientImpl;
class ContentRendererClientImpl; class ContentRendererClientImpl;
class ContentUtilityClientImpl; class ContentUtilityClientImpl;
...@@ -30,6 +30,7 @@ class ContentMainDelegateImpl : public content::ContentMainDelegate { ...@@ -30,6 +30,7 @@ class ContentMainDelegateImpl : public content::ContentMainDelegate {
int RunProcess( int RunProcess(
const std::string& process_type, const std::string& process_type,
const content::MainFunctionParams& main_function_params) override; const content::MainFunctionParams& main_function_params) override;
content::ContentClient* CreateContentClient() override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
content::ContentUtilityClient* CreateContentUtilityClient() override; content::ContentUtilityClient* CreateContentUtilityClient() override;
......
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