Commit 26f0b3a7 authored by rogerta@chromium.org's avatar rogerta@chromium.org

Setup from settings should allow configuration first.

BUG=81265
TEST=When setting up sync using the gaia-based web flow, the user should have
the option to configure sync before it starts.  The UX should be same as the
native ui flow.


Review URL: https://chromiumcodereview.appspot.com/11418200

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@170999 0039d316-1c4b-4281-b951-d872f2087c98
parent e6de485e
...@@ -108,10 +108,8 @@ void StartSync(Browser* browser, ...@@ -108,10 +108,8 @@ void StartSync(Browser* browser,
} }
bool UseWebBasedSigninFlow() { bool UseWebBasedSigninFlow() {
static const bool use_web_based_singin_flow = return CommandLine::ForCurrentProcess()->HasSwitch(
CommandLine::ForCurrentProcess()->HasSwitch( switches::kUseWebBasedSigninFlow);
switches::kUseWebBasedSigninFlow);
return use_web_based_singin_flow;
} }
// Determines the source of the sign in. Its either one of the known sign in // Determines the source of the sign in. Its either one of the known sign in
...@@ -439,20 +437,22 @@ bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents, ...@@ -439,20 +437,22 @@ bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents,
} }
} }
// If we're about to show a one-click infobar but the user has started if (!UseWebBasedSigninFlow()) {
// a concurrent signin flow (perhaps via the promo), we may not have yet // If we're about to show a one-click infobar but the user has started
// established an authenticated username but we still shouldn't move // a concurrent signin flow (perhaps via the promo), we may not have yet
// forward with two simultaneous signin processes. This is a bit // established an authenticated username but we still shouldn't move
// contentious as the one-click flow is a much smoother flow from the user // forward with two simultaneous signin processes. This is a bit
// perspective, but it's much more difficult to hijack the other flow from // contentious as the one-click flow is a much smoother flow from the user
// here as it is to bail. // perspective, but it's much more difficult to hijack the other flow from
ProfileSyncService* service = // here as it is to bail.
ProfileSyncServiceFactory::GetForProfile(profile); ProfileSyncService* service =
if (!service) ProfileSyncServiceFactory::GetForProfile(profile);
return false; if (!service)
return false;
if (service->FirstSetupInProgress())
return false; if (service->FirstSetupInProgress())
return false;
}
} }
return true; return true;
...@@ -724,7 +724,9 @@ void OneClickSigninHelper::DidStopLoading( ...@@ -724,7 +724,9 @@ void OneClickSigninHelper::DidStopLoading(
break; break;
case AUTO_ACCEPT_EXPLICIT: case AUTO_ACCEPT_EXPLICIT:
StartSync(browser, auto_accept_, session_index_, email_, password_, StartSync(browser, auto_accept_, session_index_, email_, password_,
OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); source_ == SyncPromoUI::SOURCE_SETTINGS ?
OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
break; break;
case REJECTED_FOR_PROFILE: case REJECTED_FOR_PROFILE:
UMA_HISTOGRAM_ENUMERATION("AutoLogin.Reverse", UMA_HISTOGRAM_ENUMERATION("AutoLogin.Reverse",
...@@ -736,30 +738,20 @@ void OneClickSigninHelper::DidStopLoading( ...@@ -736,30 +738,20 @@ void OneClickSigninHelper::DidStopLoading(
break; break;
} }
AutoAccept local_auto_accept = auto_accept_; // If this explicit sign in is not from settings page, show the NTP after
SyncPromoUI::Source local_source = source_; // sign in completes. In the case of the settings page, it will get closed
// by SyncSetupHandler.
if (auto_accept_ == AUTO_ACCEPT_EXPLICIT &&
source_ != SyncPromoUI::SOURCE_SETTINGS) {
Profile* profile =
Profile::FromBrowserContext(contents->GetBrowserContext());
signin_tracker_.reset(new SigninTracker(profile, this));
}
email_.clear(); email_.clear();
password_.clear(); password_.clear();
auto_accept_ = NO_AUTO_ACCEPT; auto_accept_ = NO_AUTO_ACCEPT;
source_ = SyncPromoUI::SOURCE_UNKNOWN; source_ = SyncPromoUI::SOURCE_UNKNOWN;
// If this is an explicit sign in by the user, then redirect them to the
// NTP.
if (local_auto_accept == AUTO_ACCEPT_EXPLICIT) {
// If this is an explicit sign in from settings page, then close the
// tab. Otherwise show the NTP after sign in completes.
if (local_source == SyncPromoUI::SOURCE_SETTINGS) {
contents->Close();
} else {
Profile* profile =
Profile::FromBrowserContext(contents->GetBrowserContext());
signin_tracker_.reset(new SigninTracker(profile, this));
}
}
// |this| may have been deleted due to the contents->Close() call above.
// No members should be accessed at this point.
} }
void OneClickSigninHelper::GaiaCredentialsValid() { void OneClickSigninHelper::GaiaCredentialsValid() {
......
...@@ -48,6 +48,11 @@ const char kImplicitURLString[] = ...@@ -48,6 +48,11 @@ const char kImplicitURLString[] =
"https://accounts.google.com/ServiceLogin" "https://accounts.google.com/ServiceLogin"
"?service=foo&continue=http://foo.google.com"; "?service=foo&continue=http://foo.google.com";
bool UseWebBasedSigninFlow() {
return CommandLine::ForCurrentProcess()->HasSwitch(
switches::kUseWebBasedSigninFlow);
}
class SigninManagerMock : public FakeSigninManager { class SigninManagerMock : public FakeSigninManager {
public: public:
explicit SigninManagerMock(Profile* profile) explicit SigninManagerMock(Profile* profile)
...@@ -352,21 +357,22 @@ TEST_F(OneClickSigninHelperTest, CanOfferFirstSetup) { ...@@ -352,21 +357,22 @@ TEST_F(OneClickSigninHelperTest, CanOfferFirstSetup) {
EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)).
WillRepeatedly(Return(true)); WillRepeatedly(Return(true));
// Invoke OneClickTestProfileSyncService factory function and grab result. // Invoke OneClickTestProfileSyncService factory function and grab result.
OneClickTestProfileSyncService* sync = OneClickTestProfileSyncService* sync =
static_cast<OneClickTestProfileSyncService*>( static_cast<OneClickTestProfileSyncService*>(
ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse( ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
static_cast<Profile*>(browser_context()), static_cast<Profile*>(browser_context()),
OneClickTestProfileSyncService::Build)); OneClickTestProfileSyncService::Build));
sync->set_first_setup_in_progress(true); sync->set_first_setup_in_progress(true);
EXPECT_FALSE(OneClickSigninHelper::CanOffer(web_contents(), EXPECT_EQ(UseWebBasedSigninFlow(),
"foo@gmail.com", OneClickSigninHelper::CanOffer(web_contents(),
true)); "foo@gmail.com",
EXPECT_TRUE(OneClickSigninHelper::CanOffer(web_contents(), true));
"foo@gmail.com", EXPECT_TRUE(OneClickSigninHelper::CanOffer(web_contents(),
false)); "foo@gmail.com",
false));
} }
TEST_F(OneClickSigninHelperTest, CanOfferProfileConnected) { TEST_F(OneClickSigninHelperTest, CanOfferProfileConnected) {
......
...@@ -59,18 +59,23 @@ void OneClickSigninSyncStarter::SigninSuccess() { ...@@ -59,18 +59,23 @@ void OneClickSigninSyncStarter::SigninSuccess() {
ProfileSyncService* profile_sync_service = ProfileSyncService* profile_sync_service =
ProfileSyncServiceFactory::GetForProfile(browser_->profile()); ProfileSyncServiceFactory::GetForProfile(browser_->profile());
if (start_mode_ == SYNC_WITH_DEFAULT_SETTINGS) { switch (start_mode_) {
// Just kick off the sync machine, no need to configure it first. case SYNC_WITH_DEFAULT_SETTINGS:
profile_sync_service->OnUserChoseDatatypes(true, syncer::ModelTypeSet()); // Just kick off the sync machine, no need to configure it first.
profile_sync_service->SetSyncSetupCompleted(); profile_sync_service->OnUserChoseDatatypes(true, syncer::ModelTypeSet());
profile_sync_service->SetSetupInProgress(false); profile_sync_service->SetSyncSetupCompleted();
} else { profile_sync_service->SetSetupInProgress(false);
// Give the user a chance to configure things. We don't clear the break;
// ProfileSyncService::setup_in_progress flag because we don't want sync case CONFIGURE_SYNC_FIRST:
// to start up until after the configure UI is displayed (the configure UI // Give the user a chance to configure things. We don't clear the
// will clear the flag when the user is done setting up sync). // ProfileSyncService::setup_in_progress flag because we don't want sync
LoginUIServiceFactory::GetForProfile(browser_->profile())->ShowLoginUI( // to start up until after the configure UI is displayed (the configure UI
browser_); // will clear the flag when the user is done setting up sync).
LoginUIServiceFactory::GetForProfile(browser_->profile())->ShowLoginUI(
browser_);
break;
default:
NOTREACHED() << "Invalid start_mode=" << start_mode_;
} }
delete this; delete this;
......
...@@ -16,7 +16,16 @@ class Browser; ...@@ -16,7 +16,16 @@ class Browser;
// the job is done. // the job is done.
class OneClickSigninSyncStarter : public SigninTracker::Observer { class OneClickSigninSyncStarter : public SigninTracker::Observer {
public: public:
enum StartSyncMode {SYNC_WITH_DEFAULT_SETTINGS, CONFIGURE_SYNC_FIRST }; enum StartSyncMode {
// Starts the process of signing the user in with the SigninManager, and
// once completed automatically starts sync with all data types enabled.
SYNC_WITH_DEFAULT_SETTINGS,
// Starts the process of signing the user in with the SigninManager, and
// once completed redirects the user to the settings page to allow them
// to configure which data types to sync before sync is enabled.
CONFIGURE_SYNC_FIRST
};
OneClickSigninSyncStarter(Browser* browser, OneClickSigninSyncStarter(Browser* browser,
const std::string& session_index, const std::string& session_index,
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#include "chrome/browser/ui/webui/sync_promo/sync_promo_ui.h" #include "chrome/browser/ui/webui/sync_promo/sync_promo_ui.h"
...@@ -191,13 +192,26 @@ bool UseWebBasedSigninFlow() { ...@@ -191,13 +192,26 @@ bool UseWebBasedSigninFlow() {
switches::kUseWebBasedSigninFlow); switches::kUseWebBasedSigninFlow);
} }
void BringTabToFront(WebContents* web_contents) {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (browser) {
TabStripModel* tab_strip_model = browser->tab_strip_model();
if (tab_strip_model) {
int index = tab_strip_model->GetIndexOfWebContents(web_contents);
if (index != TabStripModel::kNoTab)
tab_strip_model->ActivateTabAt(index, false);
}
}
}
} // namespace } // namespace
SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager) SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager)
: configuring_sync_(false), : configuring_sync_(false),
profile_manager_(profile_manager), profile_manager_(profile_manager),
last_signin_error_(GoogleServiceAuthError::NONE), last_signin_error_(GoogleServiceAuthError::NONE),
retry_on_signin_failure_(true) { retry_on_signin_failure_(true),
active_gaia_signin_tab_(NULL) {
} }
SyncSetupHandler::~SyncSetupHandler() { SyncSetupHandler::~SyncSetupHandler() {
...@@ -489,6 +503,12 @@ void SyncSetupHandler::DisplayConfigureSync(bool show_advanced, ...@@ -489,6 +503,12 @@ void SyncSetupHandler::DisplayConfigureSync(bool show_advanced,
StringValue page("configure"); StringValue page("configure");
web_ui()->CallJavascriptFunction( web_ui()->CallJavascriptFunction(
"SyncSetupOverlay.showSyncSetupPage", page, args); "SyncSetupOverlay.showSyncSetupPage", page, args);
if (UseWebBasedSigninFlow()) {
// Make sure the tab used for the Gaia sign in does not cover the settings
// tab.
BringTabToFront(web_ui()->GetWebContents());
}
} }
void SyncSetupHandler::ConfigureSyncDone() { void SyncSetupHandler::ConfigureSyncDone() {
...@@ -566,13 +586,25 @@ SigninManager* SyncSetupHandler::GetSignin() const { ...@@ -566,13 +586,25 @@ SigninManager* SyncSetupHandler::GetSignin() const {
void SyncSetupHandler::DisplayGaiaLogin(bool fatal_error) { void SyncSetupHandler::DisplayGaiaLogin(bool fatal_error) {
if (UseWebBasedSigninFlow()) { if (UseWebBasedSigninFlow()) {
DCHECK(!active_gaia_signin_tab_);
// Advanced options are no longer being configured if the login screen is
// visible. If the user exits the signin wizard after this without
// configuring sync, CloseSyncSetup() will ensure they are logged out.
configuring_sync_ = false;
GURL url(SyncPromoUI::GetSyncPromoURL(GURL(), GURL url(SyncPromoUI::GetSyncPromoURL(GURL(),
SyncPromoUI::SOURCE_SETTINGS, false)); SyncPromoUI::SOURCE_SETTINGS, false));
Browser* browser = chrome::FindBrowserWithWebContents( Browser* browser = chrome::FindBrowserWithWebContents(
web_ui()->GetWebContents()); web_ui()->GetWebContents());
browser->OpenURL( active_gaia_signin_tab_ = browser->OpenURL(
content::OpenURLParams(url, content::Referrer(), SINGLETON_TAB, content::OpenURLParams(url, content::Referrer(), SINGLETON_TAB,
content::PAGE_TRANSITION_AUTO_BOOKMARK, false)); content::PAGE_TRANSITION_AUTO_BOOKMARK,
false));
content::WebContentsObserver::Observe(active_gaia_signin_tab_);
signin_tracker_.reset(
new SigninTracker(GetProfile(), this,
SigninTracker::WAITING_FOR_GAIA_VALIDATION));
} else { } else {
retry_on_signin_failure_ = true; retry_on_signin_failure_ = true;
DisplayGaiaLoginWithErrorMessage(string16(), fatal_error); DisplayGaiaLoginWithErrorMessage(string16(), fatal_error);
...@@ -581,9 +613,9 @@ void SyncSetupHandler::DisplayGaiaLogin(bool fatal_error) { ...@@ -581,9 +613,9 @@ void SyncSetupHandler::DisplayGaiaLogin(bool fatal_error) {
void SyncSetupHandler::DisplayGaiaLoginWithErrorMessage( void SyncSetupHandler::DisplayGaiaLoginWithErrorMessage(
const string16& error_message, bool fatal_error) { const string16& error_message, bool fatal_error) {
// We are no longer configuring sync if the login screen is visible. // Advanced options are no longer being configured if the login screen is
// If the user exits the signin wizard after this without configuring sync, // visible. If the user exits the signin wizard after this without
// CloseSyncSetup() will ensure they are logged out. // configuring sync, CloseSyncSetup() will ensure they are logged out.
configuring_sync_ = false; configuring_sync_ = false;
string16 local_error_message(error_message); string16 local_error_message(error_message);
...@@ -662,11 +694,9 @@ bool SyncSetupHandler::PrepareSyncSetup() { ...@@ -662,11 +694,9 @@ bool SyncSetupHandler::PrepareSyncSetup() {
return false; return false;
} }
if (!UseWebBasedSigninFlow()) { // Notify services that login UI is now active.
// Notify services that login UI is now active. GetLoginUIService()->SetLoginUI(this);
GetLoginUIService()->SetLoginUI(this); service->SetSetupInProgress(true);
service->SetSetupInProgress(true);
}
return true; return true;
} }
...@@ -713,6 +743,9 @@ void SyncSetupHandler::DisplayGaiaSuccessAndClose() { ...@@ -713,6 +743,9 @@ void SyncSetupHandler::DisplayGaiaSuccessAndClose() {
void SyncSetupHandler::DisplayGaiaSuccessAndSettingUp() { void SyncSetupHandler::DisplayGaiaSuccessAndSettingUp() {
RecordSignin(); RecordSignin();
if (UseWebBasedSigninFlow())
CloseGaiaSigninPage();
web_ui()->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndSettingUp"); web_ui()->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndSettingUp");
} }
...@@ -1013,6 +1046,7 @@ void SyncSetupHandler::CloseSyncSetup() { ...@@ -1013,6 +1046,7 @@ void SyncSetupHandler::CloseSyncSetup() {
ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH); ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH);
} }
} }
// Let the various services know that we're no longer active. // Let the various services know that we're no longer active.
GetLoginUIService()->LoginUIClosed(this); GetLoginUIService()->LoginUIClosed(this);
} }
...@@ -1090,8 +1124,12 @@ void SyncSetupHandler::OpenConfigureSync() { ...@@ -1090,8 +1124,12 @@ void SyncSetupHandler::OpenConfigureSync() {
void SyncSetupHandler::FocusUI() { void SyncSetupHandler::FocusUI() {
DCHECK(IsActiveLogin()); DCHECK(IsActiveLogin());
WebContents* web_contents = web_ui()->GetWebContents(); if (UseWebBasedSigninFlow() && signin_tracker_) {
web_contents->GetDelegate()->ActivateContents(web_contents); BringTabToFront(active_gaia_signin_tab_);
} else {
WebContents* web_contents = web_ui()->GetWebContents();
web_contents->GetDelegate()->ActivateContents(web_contents);
}
} }
void SyncSetupHandler::CloseUI() { void SyncSetupHandler::CloseUI() {
...@@ -1099,6 +1137,12 @@ void SyncSetupHandler::CloseUI() { ...@@ -1099,6 +1137,12 @@ void SyncSetupHandler::CloseUI() {
CloseOverlay(); CloseOverlay();
} }
void SyncSetupHandler::WebContentsDestroyed(
content::WebContents* web_contents) {
DCHECK(active_gaia_signin_tab_);
CloseSyncSetup();
}
// Private member functions. // Private member functions.
bool SyncSetupHandler::FocusExistingWizardIfPresent() { bool SyncSetupHandler::FocusExistingWizardIfPresent() {
...@@ -1121,6 +1165,28 @@ void SyncSetupHandler::CloseOverlay() { ...@@ -1121,6 +1165,28 @@ void SyncSetupHandler::CloseOverlay() {
web_ui()->CallJavascriptFunction("OptionsPage.closeOverlay"); web_ui()->CallJavascriptFunction("OptionsPage.closeOverlay");
} }
void SyncSetupHandler::CloseGaiaSigninPage() {
if (active_gaia_signin_tab_) {
content::WebContentsObserver::Observe(NULL);
Browser* browser = chrome::FindBrowserWithWebContents(
active_gaia_signin_tab_);
if (browser) {
TabStripModel* tab_strip_model = browser->tab_strip_model();
if (tab_strip_model) {
int index = tab_strip_model->GetIndexOfWebContents(
active_gaia_signin_tab_);
if (index != TabStripModel::kNoTab) {
tab_strip_model->ExecuteContextMenuCommand(
index, TabStripModel::CommandCloseTab);
}
}
}
}
active_gaia_signin_tab_ = NULL;
}
bool SyncSetupHandler::IsLoginAuthDataValid(const std::string& username, bool SyncSetupHandler::IsLoginAuthDataValid(const std::string& username,
string16* error_message) { string16* error_message) {
if (username.empty()) if (username.empty())
......
...@@ -11,15 +11,21 @@ ...@@ -11,15 +11,21 @@
#include "chrome/browser/signin/signin_tracker.h" #include "chrome/browser/signin/signin_tracker.h"
#include "chrome/browser/ui/webui/options/options_ui.h" #include "chrome/browser/ui/webui/options/options_ui.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h"
#include "content/public/browser/web_contents_observer.h"
class LoginUIService; class LoginUIService;
class ProfileManager; class ProfileManager;
class ProfileSyncService; class ProfileSyncService;
class SigninManager; class SigninManager;
namespace content {
class WebContents;
}
class SyncSetupHandler : public options::OptionsPageUIHandler, class SyncSetupHandler : public options::OptionsPageUIHandler,
public SigninTracker::Observer, public SigninTracker::Observer,
public LoginUIService::LoginUI { public LoginUIService::LoginUI,
public content::WebContentsObserver {
public: public:
// Constructs a new SyncSetupHandler. |profile_manager| may be NULL. // Constructs a new SyncSetupHandler. |profile_manager| may be NULL.
explicit SyncSetupHandler(ProfileManager* profile_manager); explicit SyncSetupHandler(ProfileManager* profile_manager);
...@@ -39,6 +45,10 @@ class SyncSetupHandler : public options::OptionsPageUIHandler, ...@@ -39,6 +45,10 @@ class SyncSetupHandler : public options::OptionsPageUIHandler,
virtual void FocusUI() OVERRIDE; virtual void FocusUI() OVERRIDE;
virtual void CloseUI() OVERRIDE; virtual void CloseUI() OVERRIDE;
// content::WebContentsObserver implementation.
virtual void WebContentsDestroyed(
content::WebContents* web_contents) OVERRIDE;
static void GetStaticLocalizedValues( static void GetStaticLocalizedValues(
base::DictionaryValue* localized_strings, base::DictionaryValue* localized_strings,
content::WebUI* web_ui); content::WebUI* web_ui);
...@@ -164,6 +174,10 @@ class SyncSetupHandler : public options::OptionsPageUIHandler, ...@@ -164,6 +174,10 @@ class SyncSetupHandler : public options::OptionsPageUIHandler,
// Invokes the javascript call to close the setup overlay. // Invokes the javascript call to close the setup overlay.
void CloseOverlay(); void CloseOverlay();
// When using web-flow, closes the Gaia page used to collection user
// credentials.
void CloseGaiaSigninPage();
// Returns true if the given login data is valid, false otherwise. If the // Returns true if the given login data is valid, false otherwise. If the
// login data is not valid then on return |error_message| will be set to a // login data is not valid then on return |error_message| will be set to a
// localized error message. Note, |error_message| must not be NULL. // localized error message. Note, |error_message| must not be NULL.
...@@ -201,6 +215,10 @@ class SyncSetupHandler : public options::OptionsPageUIHandler, ...@@ -201,6 +215,10 @@ class SyncSetupHandler : public options::OptionsPageUIHandler,
// service. // service.
scoped_ptr<base::OneShotTimer<SyncSetupHandler> > backend_start_timer_; scoped_ptr<base::OneShotTimer<SyncSetupHandler> > backend_start_timer_;
// When using web-flow, weak pointer to the tab that holds the Gaia sign in
// page.
content::WebContents* active_gaia_signin_tab_;
DISALLOW_COPY_AND_ASSIGN(SyncSetupHandler); DISALLOW_COPY_AND_ASSIGN(SyncSetupHandler);
}; };
......
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