Commit 4b7ca62f authored by Yunke Zhou's avatar Yunke Zhou Committed by Chromium LUCI CQ

Oobe: move EnableDebuggingScreenHandler logic to screen object

Bug: 1145539
Change-Id: I1e75c7517e56a858478d6fd3681d3dfccad3779d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2563474Reviewed-by: default avatarDenis Kuznetsov [CET] <antrim@chromium.org>
Commit-Queue: Yunke Zhou <yunkez@google.com>
Cr-Commit-Position: refs/heads/master@{#832359}
parent 09774812
......@@ -5,7 +5,26 @@
#include "chrome/browser/chromeos/login/screens/enable_debugging_screen.h"
#include "base/check.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/login_web_dialog.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/dbus/cryptohome/cryptohome_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
constexpr char kUserActionCancel[] = "cancel";
constexpr char kUserActionDone[] = "done";
constexpr char kUserActionLearnMore[] = "learnMore";
constexpr char kUserActionRemoveRootFSProtection[] = "removeRootFSProtection";
} // namespace
namespace chromeos {
......@@ -26,20 +45,16 @@ EnableDebuggingScreen::~EnableDebuggingScreen() {
view_->SetDelegate(nullptr);
}
void EnableDebuggingScreen::OnExit(bool success) {
if (is_hidden())
return;
exit_callback_.Run();
}
void EnableDebuggingScreen::OnViewDestroyed(EnableDebuggingScreenView* view) {
if (view_ == view)
view_ = nullptr;
}
void EnableDebuggingScreen::ShowImpl() {
if (view_)
if (view_) {
view_->Show();
WaitForCryptohome();
}
}
void EnableDebuggingScreen::HideImpl() {
......@@ -47,4 +62,153 @@ void EnableDebuggingScreen::HideImpl() {
view_->Hide();
}
void EnableDebuggingScreen::OnUserAction(const std::string& action_id) {
if (action_id == kUserActionCancel || action_id == kUserActionDone) {
exit_callback_.Run();
} else if (action_id == kUserActionLearnMore) {
HandleLearnMore();
} else if (action_id == kUserActionRemoveRootFSProtection) {
HandleRemoveRootFSProtection();
} else {
BaseScreen::OnUserAction(action_id);
}
}
void EnableDebuggingScreen::HandleLearnMore() {
VLOG(1) << "Trying to view the help article about debugging features.";
const std::string help_content =
l10n_util::GetStringUTF8(IDS_ENABLE_DEBUGGING_HELP);
const GURL data_url = GURL("data:text/html;charset=utf-8," + help_content);
LoginWebDialog* dialog = new LoginWebDialog(
Profile::FromWebUI(
LoginDisplayHost::default_host()->GetOobeUI()->web_ui()),
nullptr, LoginDisplayHost::default_host()->GetNativeWindow(),
base::string16(), data_url);
dialog->Show();
}
void EnableDebuggingScreen::HandleRemoveRootFSProtection() {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_WAIT);
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->RemoveRootfsVerification(
base::BindOnce(&EnableDebuggingScreen::OnRemoveRootfsVerification,
weak_ptr_factory_.GetWeakPtr()));
}
// Removes rootfs verification, add flag to start with enable debugging features
// screen and reboots the machine.
void EnableDebuggingScreen::OnRemoveRootfsVerification(bool success) {
if (!success) {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_ERROR);
return;
}
PrefService* prefs = g_browser_process->local_state();
prefs->SetBoolean(prefs::kDebuggingFeaturesRequested, true);
prefs->CommitPendingWrite();
chromeos::PowerManagerClient::Get()->RequestRestart(
power_manager::REQUEST_RESTART_OTHER,
"login debugging screen removing rootfs verification");
}
void EnableDebuggingScreen::WaitForCryptohome() {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_WAIT);
chromeos::CryptohomeClient* client = chromeos::CryptohomeClient::Get();
client->WaitForServiceToBeAvailable(base::BindOnce(
&EnableDebuggingScreen::OnCryptohomeDaemonAvailabilityChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void EnableDebuggingScreen::OnCryptohomeDaemonAvailabilityChecked(
bool service_is_available) {
DVLOG(1) << "Enable-debugging-screen: cryptohomed availability="
<< service_is_available;
if (!service_is_available) {
LOG(ERROR) << "Crypthomed is not available.";
UpdateUIState(EnableDebuggingScreenView::UI_STATE_ERROR);
return;
}
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->WaitForServiceToBeAvailable(base::BindOnce(
&EnableDebuggingScreen::OnDebugDaemonServiceAvailabilityChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void EnableDebuggingScreen::OnDebugDaemonServiceAvailabilityChecked(
bool service_is_available) {
DVLOG(1) << "Enable-debugging-screen: debugd availability="
<< service_is_available;
if (!service_is_available) {
LOG(ERROR) << "Debug daemon is not available.";
UpdateUIState(EnableDebuggingScreenView::UI_STATE_ERROR);
return;
}
// Check the status of debugging features.
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->QueryDebuggingFeatures(
base::BindOnce(&EnableDebuggingScreen::OnQueryDebuggingFeatures,
weak_ptr_factory_.GetWeakPtr()));
}
void EnableDebuggingScreen::OnQueryDebuggingFeatures(bool success,
int features_flag) {
DVLOG(1) << "Enable-debugging-screen: OnQueryDebuggingFeatures"
<< ", success=" << success << ", features=" << features_flag;
if (!success ||
features_flag == debugd::DevFeatureFlag::DEV_FEATURES_DISABLED) {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_ERROR);
return;
}
if ((features_flag &
debugd::DevFeatureFlag::DEV_FEATURE_ROOTFS_VERIFICATION_REMOVED) == 0) {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_REMOVE_PROTECTION);
return;
}
if ((features_flag & DebugDaemonClient::DEV_FEATURE_ALL_ENABLED) !=
DebugDaemonClient::DEV_FEATURE_ALL_ENABLED) {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_SETUP);
} else {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_DONE);
}
}
void EnableDebuggingScreen::HandleSetup(const std::string& password) {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_WAIT);
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->EnableDebuggingFeatures(
password,
base::BindOnce(&EnableDebuggingScreen::OnEnableDebuggingFeatures,
weak_ptr_factory_.GetWeakPtr()));
}
void EnableDebuggingScreen::OnEnableDebuggingFeatures(bool success) {
if (!success) {
UpdateUIState(EnableDebuggingScreenView::UI_STATE_ERROR);
return;
}
UpdateUIState(EnableDebuggingScreenView::UI_STATE_DONE);
}
void EnableDebuggingScreen::UpdateUIState(
EnableDebuggingScreenView::UIState state) {
if (state == EnableDebuggingScreenView::UI_STATE_SETUP ||
state == EnableDebuggingScreenView::UI_STATE_ERROR ||
state == EnableDebuggingScreenView::UI_STATE_DONE) {
PrefService* prefs = g_browser_process->local_state();
prefs->ClearPref(prefs::kDebuggingFeaturesRequested);
prefs->CommitPendingWrite();
}
view_->UpdateUIState(state);
}
} // namespace chromeos
......@@ -9,6 +9,7 @@
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/chromeos/login/screens/base_screen.h"
#include "chrome/browser/ui/webui/chromeos/login/enable_debugging_screen_handler.h"
......@@ -23,20 +24,47 @@ class EnableDebuggingScreen : public BaseScreen {
~EnableDebuggingScreen() override;
// Called by EnableDebuggingScreenHandler.
void OnExit(bool success);
void OnViewDestroyed(EnableDebuggingScreenView* view);
void HandleSetup(const std::string& password);
protected:
// BaseScreen:
void ShowImpl() override;
void HideImpl() override;
void OnUserAction(const std::string& action_id) override;
base::RepeatingClosure* exit_callback() { return &exit_callback_; }
private:
// Handle user actions
void HandleLearnMore();
void HandleRemoveRootFSProtection();
// Wait for cryptohomed before checking debugd. See http://crbug.com/440506
void WaitForCryptohome();
// Callback for CryptohomeClient::WaitForServiceToBeAvailable
void OnCryptohomeDaemonAvailabilityChecked(bool service_is_available);
// Callback for DebugDaemonClient::WaitForServiceToBeAvailable
void OnDebugDaemonServiceAvailabilityChecked(bool service_is_available);
// Callback for DebugDaemonClient::EnableDebuggingFeatures().
void OnEnableDebuggingFeatures(bool success);
// Callback for DebugDaemonClient::QueryDebuggingFeatures().
void OnQueryDebuggingFeatures(bool success, int features_flag);
// Callback for DebugDaemonClient::RemoveRootfsVerification().
void OnRemoveRootfsVerification(bool success);
void UpdateUIState(EnableDebuggingScreenView::UIState state);
EnableDebuggingScreenView* view_;
base::RepeatingClosure exit_callback_;
base::WeakPtrFactory<EnableDebuggingScreen> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(EnableDebuggingScreen);
};
......
......@@ -20,6 +20,7 @@ class MockEnableDebuggingScreen : public EnableDebuggingScreen {
MOCK_METHOD(void, ShowImpl, ());
MOCK_METHOD(void, HideImpl, ());
MOCK_METHOD(void, OnUserAction, (const std::string& action_id));
void ExitScreen();
};
......@@ -32,6 +33,7 @@ class MockEnableDebuggingScreenView : public EnableDebuggingScreenView {
MOCK_METHOD(void, Show, ());
MOCK_METHOD(void, Hide, ());
MOCK_METHOD(void, MockSetDelegate, (EnableDebuggingScreen * screen));
MOCK_METHOD(void, UpdateUIState, (UIState state));
void SetDelegate(EnableDebuggingScreen* screen) override;
......
......@@ -87,7 +87,7 @@ Polymer({
* network settings.
*/
cancel() {
chrome.send('enableDebuggingOnCancel');
this.userActed('cancel');
},
/**
......@@ -112,11 +112,11 @@ Polymer({
},
onHelpLinkClicked_() {
chrome.send('enableDebuggingOnLearnMore');
this.userActed('learnMore');
},
onRemoveButtonClicked_() {
chrome.send('enableDebuggingOnRemoveRootFSProtection');
this.userActed('removeRootFSProtection');
},
onEnableButtonClicked_() {
......@@ -126,7 +126,7 @@ Polymer({
},
onOKButtonClicked_() {
chrome.send('enableDebuggingOnDone');
this.userActed('done');
},
});
......
......@@ -6,29 +6,15 @@
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/oobe_screen.h"
#include "chrome/browser/chromeos/login/screens/enable_debugging_screen.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/login_web_dialog.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/constants/chromeos_switches.h"
#include "chromeos/dbus/cryptohome/cryptohome_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/login/localized_values_builder.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/l10n/l10n_util.h"
namespace chromeos {
......@@ -36,42 +22,30 @@ constexpr StaticOobeScreenId EnableDebuggingScreenView::kScreenId;
EnableDebuggingScreenHandler::EnableDebuggingScreenHandler(
JSCallsContainer* js_calls_container)
: BaseScreenHandler(kScreenId, js_calls_container) {}
: BaseScreenHandler(kScreenId, js_calls_container) {
set_user_acted_method_path("login.EnableDebuggingScreen.userActed");
}
EnableDebuggingScreenHandler::~EnableDebuggingScreenHandler() {
if (screen_)
screen_->OnViewDestroyed(this);
}
void EnableDebuggingScreenHandler::ShowWithParams() {
ShowScreen(kScreenId);
UpdateUIState(UI_STATE_WAIT);
DVLOG(1) << "Showing enable debugging screen.";
// Wait for cryptohomed before checking debugd. See http://crbug.com/440506.
chromeos::CryptohomeClient* client = chromeos::CryptohomeClient::Get();
client->WaitForServiceToBeAvailable(base::BindOnce(
&EnableDebuggingScreenHandler::OnCryptohomeDaemonAvailabilityChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void EnableDebuggingScreenHandler::Show() {
if (!page_is_ready()) {
show_on_init_ = true;
return;
}
ShowWithParams();
DVLOG(1) << "Showing enable debugging screen.";
ShowScreen(kScreenId);
}
void EnableDebuggingScreenHandler::Hide() {
weak_ptr_factory_.InvalidateWeakPtrs();
}
void EnableDebuggingScreenHandler::Hide() {}
void EnableDebuggingScreenHandler::SetDelegate(EnableDebuggingScreen* screen) {
screen_ = screen;
BaseScreenHandler::SetBaseScreen(screen_);
if (page_is_ready())
Initialize();
}
......@@ -131,157 +105,18 @@ void EnableDebuggingScreenHandler::Initialize() {
}
void EnableDebuggingScreenHandler::RegisterMessages() {
AddCallback("enableDebuggingOnCancel",
&EnableDebuggingScreenHandler::HandleOnCancel);
AddCallback("enableDebuggingOnDone",
&EnableDebuggingScreenHandler::HandleOnDone);
AddCallback("enableDebuggingOnLearnMore",
&EnableDebuggingScreenHandler::HandleOnLearnMore);
AddCallback("enableDebuggingOnRemoveRootFSProtection",
&EnableDebuggingScreenHandler::HandleOnRemoveRootFSProtection);
BaseScreenHandler::RegisterMessages();
AddCallback("enableDebuggingOnSetup",
&EnableDebuggingScreenHandler::HandleOnSetup);
}
void EnableDebuggingScreenHandler::HandleOnCancel() {
if (screen_)
screen_->OnExit(false);
}
void EnableDebuggingScreenHandler::HandleOnDone() {
if (screen_)
screen_->OnExit(true);
}
void EnableDebuggingScreenHandler::HandleOnRemoveRootFSProtection() {
UpdateUIState(UI_STATE_WAIT);
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->RemoveRootfsVerification(
base::BindOnce(&EnableDebuggingScreenHandler::OnRemoveRootfsVerification,
weak_ptr_factory_.GetWeakPtr()));
}
void EnableDebuggingScreenHandler::HandleOnSetup(
const std::string& password) {
UpdateUIState(UI_STATE_WAIT);
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->EnableDebuggingFeatures(
password,
base::BindOnce(&EnableDebuggingScreenHandler::OnEnableDebuggingFeatures,
weak_ptr_factory_.GetWeakPtr()));
screen_->HandleSetup(password);
}
void EnableDebuggingScreenHandler::OnCryptohomeDaemonAvailabilityChecked(
bool service_is_available) {
DVLOG(1) << "Enable-debugging-screen: cryptohomed availability="
<< service_is_available;
if (!service_is_available) {
LOG(ERROR) << "Crypthomed is not available.";
UpdateUIState(UI_STATE_ERROR);
return;
}
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->WaitForServiceToBeAvailable(base::BindOnce(
&EnableDebuggingScreenHandler::OnDebugDaemonServiceAvailabilityChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void EnableDebuggingScreenHandler::OnDebugDaemonServiceAvailabilityChecked(
bool service_is_available) {
DVLOG(1) << "Enable-debugging-screen: debugd availability="
<< service_is_available;
if (!service_is_available) {
LOG(ERROR) << "Debug daemon is not available.";
UpdateUIState(UI_STATE_ERROR);
return;
}
// Check the status of debugging features.
chromeos::DebugDaemonClient* client =
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
client->QueryDebuggingFeatures(
base::BindOnce(&EnableDebuggingScreenHandler::OnQueryDebuggingFeatures,
weak_ptr_factory_.GetWeakPtr()));
}
// Removes rootfs verification, add flag to start with enable debugging features
// screen and reboots the machine.
void EnableDebuggingScreenHandler::OnRemoveRootfsVerification(bool success) {
if (!success) {
UpdateUIState(UI_STATE_ERROR);
return;
}
PrefService* prefs = g_browser_process->local_state();
prefs->SetBoolean(prefs::kDebuggingFeaturesRequested, true);
prefs->CommitPendingWrite();
chromeos::PowerManagerClient::Get()->RequestRestart(
power_manager::REQUEST_RESTART_OTHER,
"login debugging screen removing rootfs verification");
}
void EnableDebuggingScreenHandler::OnEnableDebuggingFeatures(bool success) {
if (!success) {
UpdateUIState(UI_STATE_ERROR);
return;
}
UpdateUIState(UI_STATE_DONE);
}
void EnableDebuggingScreenHandler::OnQueryDebuggingFeatures(bool success,
int features_flag) {
DVLOG(1) << "Enable-debugging-screen: OnQueryDebuggingFeatures"
<< ", success=" << success
<< ", features=" << features_flag;
if (!success ||
features_flag == debugd::DevFeatureFlag::DEV_FEATURES_DISABLED) {
UpdateUIState(UI_STATE_ERROR);
return;
}
if ((features_flag &
debugd::DevFeatureFlag::DEV_FEATURE_ROOTFS_VERIFICATION_REMOVED) == 0) {
UpdateUIState(UI_STATE_REMOVE_PROTECTION);
return;
}
if ((features_flag & DebugDaemonClient::DEV_FEATURE_ALL_ENABLED) !=
DebugDaemonClient::DEV_FEATURE_ALL_ENABLED) {
UpdateUIState(UI_STATE_SETUP);
} else {
UpdateUIState(UI_STATE_DONE);
}
}
void EnableDebuggingScreenHandler::UpdateUIState(
EnableDebuggingScreenHandler::UIState state) {
if (state == UI_STATE_SETUP ||
state == UI_STATE_ERROR ||
state == UI_STATE_DONE) {
PrefService* prefs = g_browser_process->local_state();
prefs->ClearPref(prefs::kDebuggingFeaturesRequested);
prefs->CommitPendingWrite();
}
void EnableDebuggingScreenHandler::UpdateUIState(UIState state) {
CallJS("login.EnableDebuggingScreen.updateState", static_cast<int>(state));
}
void EnableDebuggingScreenHandler::HandleOnLearnMore() {
VLOG(1) << "Trying to view the help article about debugging features.";
const std::string help_content =
l10n_util::GetStringUTF8(IDS_ENABLE_DEBUGGING_HELP);
const GURL data_url = GURL("data:text/html;charset=utf-8," + help_content);
LoginWebDialog* dialog =
new LoginWebDialog(Profile::FromWebUI(web_ui()), NULL,
LoginDisplayHost::default_host()->GetNativeWindow(),
base::string16(), data_url);
dialog->Show();
}
} // namespace chromeos
......@@ -8,8 +8,6 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/chromeos/login/help_app_launcher.h"
#include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
class PrefRegistrySimple;
......@@ -24,11 +22,20 @@ class EnableDebuggingScreenView {
public:
constexpr static StaticOobeScreenId kScreenId{"debugging"};
enum UIState {
UI_STATE_ERROR = -1,
UI_STATE_REMOVE_PROTECTION = 1,
UI_STATE_SETUP = 2,
UI_STATE_WAIT = 3,
UI_STATE_DONE = 4,
};
virtual ~EnableDebuggingScreenView() {}
virtual void Show() = 0;
virtual void Hide() = 0;
virtual void SetDelegate(EnableDebuggingScreen* screen) = 0;
virtual void UpdateUIState(UIState state) = 0;
};
// WebUI implementation of EnableDebuggingScreenView.
......@@ -44,6 +51,7 @@ class EnableDebuggingScreenHandler : public EnableDebuggingScreenView,
void Show() override;
void Hide() override;
void SetDelegate(EnableDebuggingScreen* screen) override;
void UpdateUIState(UIState state) override;
// BaseScreenHandler implementation:
void DeclareLocalizedValues(
......@@ -57,48 +65,14 @@ class EnableDebuggingScreenHandler : public EnableDebuggingScreenView,
static void RegisterPrefs(PrefRegistrySimple* registry);
private:
enum UIState {
UI_STATE_ERROR = -1,
UI_STATE_REMOVE_PROTECTION = 1,
UI_STATE_SETUP = 2,
UI_STATE_WAIT = 3,
UI_STATE_DONE = 4,
};
// JS messages handlers.
void HandleOnCancel();
void HandleOnDone();
void HandleOnLearnMore();
void HandleOnRemoveRootFSProtection();
void HandleOnSetup(const std::string& password);
void ShowWithParams();
// Callback for CryptohomeClient::WaitForServiceToBeAvailable
void OnCryptohomeDaemonAvailabilityChecked(bool service_is_available);
// Callback for DebugDaemonClient::WaitForServiceToBeAvailable
void OnDebugDaemonServiceAvailabilityChecked(bool service_is_available);
// Callback for DebugDaemonClient::EnableDebuggingFeatures().
void OnEnableDebuggingFeatures(bool success);
// Callback for DebugDaemonClient::QueryDebuggingFeatures().
void OnQueryDebuggingFeatures(bool success, int features_flag);
// Callback for DebugDaemonClient::RemoveRootfsVerification().
void OnRemoveRootfsVerification(bool success);
// Updates UI state.
void UpdateUIState(UIState state);
EnableDebuggingScreen* screen_ = nullptr;
// Keeps whether screen should be shown right after initialization.
bool show_on_init_ = false;
base::WeakPtrFactory<EnableDebuggingScreenHandler> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(EnableDebuggingScreenHandler);
};
......
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