Commit f68d1fb9 authored by Thomas Tangl's avatar Thomas Tangl Committed by Commit Bot

[profile-menu] Update incognito menu

 - Move all incognito code to incognito_menu_view.cc
   (to avoid special casing for ChromeOS)
 - Add "Exit incognito" button
 - Re-enable browser tests

Bug: 995720
Change-Id: I55674e9192065d4a185b712af391a56fdc56095a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1859962Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Commit-Queue: Thomas Tangl <tangltom@chromium.org>
Cr-Commit-Position: refs/heads/master@{#705544}
parent 0395f53e
......@@ -49,23 +49,40 @@ void IncognitoMenuView::BuildMenu() {
const SkColor icon_color = provider->GetTypographyProvider().GetColor(
*this, views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY);
auto incognito_icon = std::make_unique<views::ImageView>();
incognito_icon->SetImage(
gfx::CreateVectorIcon(kIncognitoProfileIcon, icon_color));
if (!base::FeatureList::IsEnabled(features::kProfileMenuRevamp)) {
auto incognito_icon = std::make_unique<views::ImageView>();
incognito_icon->SetImage(
gfx::CreateVectorIcon(kIncognitoProfileIcon, icon_color));
AddMenuGroup(false /* add_separator */);
CreateAndAddTitleCard(
std::move(incognito_icon),
AddMenuGroup(false /* add_separator */);
CreateAndAddTitleCard(
std::move(incognito_icon),
l10n_util::GetStringUTF16(IDS_INCOGNITO_PROFILE_MENU_TITLE),
incognito_window_count > 1
? l10n_util::GetPluralStringFUTF16(
IDS_INCOGNITO_WINDOW_COUNT_MESSAGE, incognito_window_count)
: base::string16(),
base::RepeatingClosure());
AddMenuGroup();
exit_button_ = CreateAndAddButton(
gfx::CreateVectorIcon(kCloseAllIcon, 16, gfx::kChromeIconGrey),
l10n_util::GetStringUTF16(IDS_INCOGNITO_PROFILE_MENU_CLOSE_BUTTON),
base::BindRepeating(&IncognitoMenuView::OnExitButtonClicked,
base::Unretained(this)));
return;
}
SetIdentityInfo(
ColoredImageForMenu(kIncognitoProfileIcon, icon_color),
/*badge=*/gfx::ImageSkia(),
l10n_util::GetStringUTF16(IDS_INCOGNITO_PROFILE_MENU_TITLE),
incognito_window_count > 1
? l10n_util::GetPluralStringFUTF16(IDS_INCOGNITO_WINDOW_COUNT_MESSAGE,
incognito_window_count)
: base::string16(),
base::RepeatingClosure());
AddMenuGroup();
exit_button_ = CreateAndAddButton(
gfx::CreateVectorIcon(kCloseAllIcon, 16, gfx::kChromeIconGrey),
: base::string16());
AddFeatureButton(
ImageForMenu(kCloseAllIcon),
l10n_util::GetStringUTF16(IDS_INCOGNITO_PROFILE_MENU_CLOSE_BUTTON),
base::BindRepeating(&IncognitoMenuView::OnExitButtonClicked,
base::Unretained(this)));
......@@ -78,6 +95,7 @@ base::string16 IncognitoMenuView::GetAccessibleWindowTitle() const {
}
void IncognitoMenuView::OnExitButtonClicked() {
RecordClick(ActionableItem::kExitProfileButton);
// Skipping before-unload trigger to give incognito mode users a chance to
// quickly close all incognito windows without needing to confirm closing the
// open forms.
......
......@@ -10,7 +10,6 @@
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "build/build_config.h"
......@@ -178,8 +177,6 @@ void ProfileMenuView::BuildMenu() {
BuildSyncInfo();
BuildFeatureButtons();
BuildAutofillButtons();
} else if (profile->IsIncognitoProfile()) {
BuildIncognitoIdentity();
} else if (profile->IsGuestSession()) {
BuildGuestIdentity();
} else {
......@@ -403,10 +400,6 @@ void ProfileMenuView::OnAddNewProfileButtonClicked() {
profiles::USER_MANAGER_OPEN_CREATE_USER_PAGE);
}
void ProfileMenuView::RecordClick(ActionableItem item) {
base::UmaHistogramEnumeration("Profile.Menu.ClickedActionableItem", item);
}
void ProfileMenuView::BuildIdentity() {
Profile* profile = browser()->profile();
signin::IdentityManager* identity_manager =
......@@ -438,19 +431,6 @@ void ProfileMenuView::BuildGuestIdentity() {
l10n_util::GetStringUTF16(IDS_GUEST_PROFILE_NAME));
}
void ProfileMenuView::BuildIncognitoIdentity() {
int incognito_window_count =
BrowserList::GetIncognitoSessionsActiveForProfile(browser()->profile());
SetIdentityInfo(
ImageForMenu(kIncognitoProfileIcon), GetSyncIcon(),
l10n_util::GetStringUTF16(IDS_INCOGNITO_PROFILE_MENU_TITLE),
incognito_window_count > 1
? l10n_util::GetPluralStringFUTF16(IDS_INCOGNITO_WINDOW_COUNT_MESSAGE,
incognito_window_count)
: base::string16());
}
gfx::ImageSkia ProfileMenuView::GetSyncIcon() {
Profile* profile = browser()->profile();
signin::IdentityManager* identity_manager =
......
......@@ -30,29 +30,6 @@ class Browser;
// It displays a list of profiles and allows users to switch between profiles.
class ProfileMenuView : public ProfileMenuViewBase, public AvatarMenuObserver {
public:
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ActionableItem {
kManageGoogleAccountButton = 0,
kPasswordsButton = 1,
kCreditCardsButton = 2,
kAddressesButton = 3,
kGuestProfileButton = 4,
kManageProfilesButton = 5,
kLockButton = 6,
kExitProfileButton = 7,
kSyncErrorButton = 8,
kCurrentProfileCard = 9,
kSigninButton = 10,
kSigninAccountButton = 11,
kSignoutButton = 12,
kOtherProfileButton = 13,
kCookiesClearedOnExitLink = 14,
kAddNewProfileButton = 15,
kSyncSettingsButton = 16,
kMaxValue = kSyncSettingsButton,
};
ProfileMenuView(views::Button* anchor_button,
Browser* browser,
signin_metrics::AccessPoint access_point);
......@@ -90,9 +67,6 @@ class ProfileMenuView : public ProfileMenuViewBase, public AvatarMenuObserver {
void OnCookiesClearedOnExitLinkClicked();
void OnAddNewProfileButtonClicked();
// Should be called inside each button/link action.
void RecordClick(ActionableItem item);
// AvatarMenuObserver:
void OnAvatarMenuChanged(AvatarMenu* avatar_menu) override;
......@@ -104,7 +78,6 @@ class ProfileMenuView : public ProfileMenuViewBase, public AvatarMenuObserver {
// Helper methods for building the menu.
void BuildIdentity();
void BuildGuestIdentity();
void BuildIncognitoIdentity();
gfx::ImageSkia GetSyncIcon();
void BuildAutofillButtons();
void BuildSyncInfo();
......
......@@ -10,6 +10,7 @@
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
......@@ -202,29 +203,17 @@ void ProfileMenuViewBase::ShowBubble(
ProfileMenuViewBase* bubble;
if (base::FeatureList::IsEnabled(features::kProfileMenuRevamp)) {
#if !defined(OS_CHROMEOS)
// On Desktop, all modes(regular, guest, incognito) are handled within
// ProfileMenuView.
bubble = new ProfileMenuView(anchor_button, browser, access_point);
#else
// On ChromeOS, only the incognito menu is implemented.
if (view_mode == profiles::BUBBLE_VIEW_MODE_INCOGNITO) {
DCHECK(browser->profile()->IsIncognitoProfile());
bubble = new IncognitoMenuView(anchor_button, browser);
#endif
} else {
if (view_mode == profiles::BUBBLE_VIEW_MODE_INCOGNITO) {
DCHECK(browser->profile()->IsIncognitoProfile());
bubble = new IncognitoMenuView(anchor_button, browser);
} else {
DCHECK_EQ(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, view_mode);
DCHECK_EQ(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, view_mode);
#if !defined(OS_CHROMEOS)
bubble = new ProfileMenuView(anchor_button, browser, access_point);
bubble = new ProfileMenuView(anchor_button, browser, access_point);
#else
NOTREACHED();
return;
NOTREACHED();
return;
#endif
}
}
views::BubbleDialogDelegateView::CreateBubble(bubble)->Show();
......@@ -527,6 +516,11 @@ gfx::ImageSkia ProfileMenuViewBase::ColoredImageForMenu(
return gfx::CreateVectorIcon(icon, kMaxImageSize, color);
}
void ProfileMenuViewBase::RecordClick(ActionableItem item) {
// TODO(tangltom): Separate metrics for incognito and guest menu.
base::UmaHistogramEnumeration("Profile.Menu.ClickedActionableItem", item);
}
ax::mojom::Role ProfileMenuViewBase::GetAccessibleWindowRole() {
// Return |ax::mojom::Role::kDialog| which will make screen readers announce
// the following in the listed order:
......
......@@ -42,6 +42,30 @@ class ProfileMenuViewBase : public content::WebContentsDelegate,
public views::StyledLabelListener,
public views::LinkListener {
public:
// Enumeration of all actionable items in the profile menu.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ActionableItem {
kManageGoogleAccountButton = 0,
kPasswordsButton = 1,
kCreditCardsButton = 2,
kAddressesButton = 3,
kGuestProfileButton = 4,
kManageProfilesButton = 5,
kLockButton = 6,
kExitProfileButton = 7,
kSyncErrorButton = 8,
kCurrentProfileCard = 9,
kSigninButton = 10,
kSigninAccountButton = 11,
kSignoutButton = 12,
kOtherProfileButton = 13,
kCookiesClearedOnExitLink = 14,
kAddNewProfileButton = 15,
kSyncSettingsButton = 16,
kMaxValue = kSyncSettingsButton,
};
// MenuItems struct keeps the menu items and meta data for a group of items in
// a menu. It takes the ownership of views and passes it to the menu when menu
// is constructed.
......@@ -127,6 +151,8 @@ class ProfileMenuViewBase : public content::WebContentsDelegate,
float icon_to_image_ratio = 1.0f);
gfx::ImageSkia ColoredImageForMenu(const gfx::VectorIcon& icon,
SkColor color);
// Should be called inside each button/link action.
void RecordClick(ActionableItem item);
// Initializes a new group of menu items. A separator is added before them if
// |add_separator| is true.
......
......@@ -600,7 +600,7 @@ INSTANTIATE_TEST_SUITE_P(,
// the correct action of the buttons in the profile menu. This is done by
// advancing the focus to each button and simulating a click. It is expected
// that each button records a histogram sample from
// |ProfileMenuView::ActionableItem|.
// |ProfileMenuViewBase::ActionableItem|.
//
// Subclasses have to implement |GetExpectedActionableItemAtIndex|. The test
// itself should contain the setup and a call to |RunTest|. Example test suite
......@@ -608,7 +608,7 @@ INSTANTIATE_TEST_SUITE_P(,
//
// class ProfileMenuClickTest_WithPrimaryAccount : public ProfileMenuClickTest {
// ...
// ProfileMenuView::ActionableItem GetExpectedActionableItemAtIndex(
// ProfileMenuViewBase::ActionableItem GetExpectedActionableItemAtIndex(
// size_t index) override {
// return ...;
// }
......@@ -643,7 +643,7 @@ class ProfileMenuClickTest : public SyncTest,
ProfileSyncServiceHarness::SigninType::FAKE_SIGNIN);
}
virtual ProfileMenuView::ActionableItem GetExpectedActionableItemAtIndex(
virtual ProfileMenuViewBase::ActionableItem GetExpectedActionableItemAtIndex(
size_t index) = 0;
// This should be called in the test body.
......@@ -737,40 +737,41 @@ class ProfileMenuClickTest : public SyncTest,
DISALLOW_COPY_AND_ASSIGN(ProfileMenuClickTest);
};
#define PROFILE_MENU_CLICK_TEST(actionable_item_list, test_case_name) \
class test_case_name : public ProfileMenuClickTest { \
public: \
test_case_name() = default; \
\
ProfileMenuView::ActionableItem GetExpectedActionableItemAtIndex( \
size_t index) override { \
return actionable_item_list[index]; \
} \
\
DISALLOW_COPY_AND_ASSIGN(test_case_name); \
}; \
\
INSTANTIATE_TEST_SUITE_P( \
, test_case_name, \
::testing::Range(size_t(0), base::size(actionable_item_list))); \
\
#define PROFILE_MENU_CLICK_TEST(actionable_item_list, test_case_name) \
class test_case_name : public ProfileMenuClickTest { \
public: \
test_case_name() = default; \
\
ProfileMenuViewBase::ActionableItem GetExpectedActionableItemAtIndex( \
size_t index) override { \
return actionable_item_list[index]; \
} \
\
DISALLOW_COPY_AND_ASSIGN(test_case_name); \
}; \
\
INSTANTIATE_TEST_SUITE_P( \
, test_case_name, \
::testing::Range(size_t(0), base::size(actionable_item_list))); \
\
IN_PROC_BROWSER_TEST_P(test_case_name, test_case_name)
// List of actionable items in the correct order as they appear in the menu.
// If a new button is added to the menu, it should also be added to this list.
constexpr ProfileMenuView::ActionableItem kActionableItems_MultipleProfiles[] =
{ProfileMenuView::ActionableItem::kPasswordsButton,
ProfileMenuView::ActionableItem::kCreditCardsButton,
ProfileMenuView::ActionableItem::kAddressesButton,
ProfileMenuView::ActionableItem::kSigninButton,
ProfileMenuView::ActionableItem::kManageProfilesButton,
ProfileMenuView::ActionableItem::kOtherProfileButton,
ProfileMenuView::ActionableItem::kOtherProfileButton,
ProfileMenuView::ActionableItem::kGuestProfileButton,
ProfileMenuView::ActionableItem::kAddNewProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuView::ActionableItem::kPasswordsButton};
constexpr ProfileMenuViewBase::ActionableItem
kActionableItems_MultipleProfiles[] = {
ProfileMenuViewBase::ActionableItem::kPasswordsButton,
ProfileMenuViewBase::ActionableItem::kCreditCardsButton,
ProfileMenuViewBase::ActionableItem::kAddressesButton,
ProfileMenuViewBase::ActionableItem::kSigninButton,
ProfileMenuViewBase::ActionableItem::kManageProfilesButton,
ProfileMenuViewBase::ActionableItem::kOtherProfileButton,
ProfileMenuViewBase::ActionableItem::kOtherProfileButton,
ProfileMenuViewBase::ActionableItem::kGuestProfileButton,
ProfileMenuViewBase::ActionableItem::kAddNewProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuViewBase::ActionableItem::kPasswordsButton};
PROFILE_MENU_CLICK_TEST(kActionableItems_MultipleProfiles,
ProfileMenuClickTest_MultipleProfiles) {
......@@ -782,18 +783,18 @@ PROFILE_MENU_CLICK_TEST(kActionableItems_MultipleProfiles,
// List of actionable items in the correct order as they appear in the menu.
// If a new button is added to the menu, it should also be added to this list.
constexpr ProfileMenuView::ActionableItem kActionableItems_SyncEnabled[] = {
ProfileMenuView::ActionableItem::kPasswordsButton,
ProfileMenuView::ActionableItem::kCreditCardsButton,
ProfileMenuView::ActionableItem::kAddressesButton,
ProfileMenuView::ActionableItem::kSyncSettingsButton,
ProfileMenuView::ActionableItem::kManageGoogleAccountButton,
ProfileMenuView::ActionableItem::kManageProfilesButton,
ProfileMenuView::ActionableItem::kGuestProfileButton,
ProfileMenuView::ActionableItem::kAddNewProfileButton,
constexpr ProfileMenuViewBase::ActionableItem kActionableItems_SyncEnabled[] = {
ProfileMenuViewBase::ActionableItem::kPasswordsButton,
ProfileMenuViewBase::ActionableItem::kCreditCardsButton,
ProfileMenuViewBase::ActionableItem::kAddressesButton,
ProfileMenuViewBase::ActionableItem::kSyncSettingsButton,
ProfileMenuViewBase::ActionableItem::kManageGoogleAccountButton,
ProfileMenuViewBase::ActionableItem::kManageProfilesButton,
ProfileMenuViewBase::ActionableItem::kGuestProfileButton,
ProfileMenuViewBase::ActionableItem::kAddNewProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuView::ActionableItem::kPasswordsButton};
ProfileMenuViewBase::ActionableItem::kPasswordsButton};
PROFILE_MENU_CLICK_TEST(kActionableItems_SyncEnabled,
ProfileMenuClickTest_SyncEnabled) {
......@@ -807,18 +808,18 @@ PROFILE_MENU_CLICK_TEST(kActionableItems_SyncEnabled,
// List of actionable items in the correct order as they appear in the menu.
// If a new button is added to the menu, it should also be added to this list.
constexpr ProfileMenuView::ActionableItem kActionableItems_SyncError[] = {
ProfileMenuView::ActionableItem::kPasswordsButton,
ProfileMenuView::ActionableItem::kCreditCardsButton,
ProfileMenuView::ActionableItem::kAddressesButton,
ProfileMenuView::ActionableItem::kSyncErrorButton,
ProfileMenuView::ActionableItem::kManageGoogleAccountButton,
ProfileMenuView::ActionableItem::kManageProfilesButton,
ProfileMenuView::ActionableItem::kGuestProfileButton,
ProfileMenuView::ActionableItem::kAddNewProfileButton,
constexpr ProfileMenuViewBase::ActionableItem kActionableItems_SyncError[] = {
ProfileMenuViewBase::ActionableItem::kPasswordsButton,
ProfileMenuViewBase::ActionableItem::kCreditCardsButton,
ProfileMenuViewBase::ActionableItem::kAddressesButton,
ProfileMenuViewBase::ActionableItem::kSyncErrorButton,
ProfileMenuViewBase::ActionableItem::kManageGoogleAccountButton,
ProfileMenuViewBase::ActionableItem::kManageProfilesButton,
ProfileMenuViewBase::ActionableItem::kGuestProfileButton,
ProfileMenuViewBase::ActionableItem::kAddNewProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuView::ActionableItem::kPasswordsButton};
ProfileMenuViewBase::ActionableItem::kPasswordsButton};
PROFILE_MENU_CLICK_TEST(kActionableItems_SyncError,
ProfileMenuClickTest_SyncError) {
......@@ -832,20 +833,20 @@ PROFILE_MENU_CLICK_TEST(kActionableItems_SyncError,
// List of actionable items in the correct order as they appear in the menu.
// If a new button is added to the menu, it should also be added to this list.
constexpr ProfileMenuView::ActionableItem
constexpr ProfileMenuViewBase::ActionableItem
kActionableItems_WithUnconsentedPrimaryAccount[] = {
ProfileMenuView::ActionableItem::kPasswordsButton,
ProfileMenuView::ActionableItem::kCreditCardsButton,
ProfileMenuView::ActionableItem::kAddressesButton,
ProfileMenuView::ActionableItem::kSigninAccountButton,
ProfileMenuView::ActionableItem::kManageGoogleAccountButton,
ProfileMenuView::ActionableItem::kSignoutButton,
ProfileMenuView::ActionableItem::kManageProfilesButton,
ProfileMenuView::ActionableItem::kGuestProfileButton,
ProfileMenuView::ActionableItem::kAddNewProfileButton,
ProfileMenuViewBase::ActionableItem::kPasswordsButton,
ProfileMenuViewBase::ActionableItem::kCreditCardsButton,
ProfileMenuViewBase::ActionableItem::kAddressesButton,
ProfileMenuViewBase::ActionableItem::kSigninAccountButton,
ProfileMenuViewBase::ActionableItem::kManageGoogleAccountButton,
ProfileMenuViewBase::ActionableItem::kSignoutButton,
ProfileMenuViewBase::ActionableItem::kManageProfilesButton,
ProfileMenuViewBase::ActionableItem::kGuestProfileButton,
ProfileMenuViewBase::ActionableItem::kAddNewProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuView::ActionableItem::kPasswordsButton};
ProfileMenuViewBase::ActionableItem::kPasswordsButton};
// TODO(crbug.com/1012167): Flaky.
PROFILE_MENU_CLICK_TEST(
......@@ -861,7 +862,7 @@ PROFILE_MENU_CLICK_TEST(
RunTest();
if (GetExpectedActionableItemAtIndex(GetParam()) ==
ProfileMenuView::ActionableItem::kSigninAccountButton) {
ProfileMenuViewBase::ActionableItem::kSigninAccountButton) {
// The sync confirmation dialog was opened after clicking the signin button
// in the profile menu. It needs to be manually dismissed to not cause any
// crashes during shutdown.
......@@ -872,13 +873,13 @@ PROFILE_MENU_CLICK_TEST(
// List of actionable items in the correct order as they appear in the menu.
// If a new button is added to the menu, it should also be added to this list.
constexpr ProfileMenuView::ActionableItem kActionableItems_GuestProfile[] = {
ProfileMenuView::ActionableItem::kManageProfilesButton,
ProfileMenuView::ActionableItem::kOtherProfileButton,
ProfileMenuView::ActionableItem::kAddNewProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuView::ActionableItem::kManageProfilesButton};
constexpr ProfileMenuViewBase::ActionableItem kActionableItems_GuestProfile[] =
{ProfileMenuViewBase::ActionableItem::kManageProfilesButton,
ProfileMenuViewBase::ActionableItem::kOtherProfileButton,
ProfileMenuViewBase::ActionableItem::kAddNewProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuViewBase::ActionableItem::kManageProfilesButton};
PROFILE_MENU_CLICK_TEST(kActionableItems_GuestProfile,
ProfileMenuClickTest_GuestProfile) {
......@@ -894,17 +895,15 @@ PROFILE_MENU_CLICK_TEST(kActionableItems_GuestProfile,
// List of actionable items in the correct order as they appear in the menu.
// If a new button is added to the menu, it should also be added to this list.
constexpr ProfileMenuView::ActionableItem kActionableItems_IncognitoProfile[] =
{ProfileMenuView::ActionableItem::kPasswordsButton,
ProfileMenuView::ActionableItem::kCreditCardsButton,
ProfileMenuView::ActionableItem::kAddressesButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuView::ActionableItem::kPasswordsButton};
constexpr ProfileMenuViewBase::ActionableItem
kActionableItems_IncognitoProfile[] = {
ProfileMenuViewBase::ActionableItem::kExitProfileButton,
// The first button is added again to finish the cycle and test that
// there are no other buttons at the end.
ProfileMenuViewBase::ActionableItem::kExitProfileButton};
// TODO(crbug.com/1012167): Flaky.
PROFILE_MENU_CLICK_TEST(kActionableItems_IncognitoProfile,
DISABLED_ProfileMenuClickTest_IncognitoProfile) {
ProfileMenuClickTest_IncognitoProfile) {
SetTargetBrowser(CreateIncognitoBrowser(browser()->profile()));
RunTest();
......
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