Commit 609c2d1d authored by nkostylev's avatar nkostylev Committed by Commit bot

[session_manager] Move user session initialization code out of ExistingUserController

Added UserSessionManager::InitializeUserSession()
(1) Launches browser for most common case
(2) Continues new user sign in with TOS (public sessions)/image screen (new users)
(3) For kiosk flow delegates launching app to the existing kiosk initialization flow

(1) Currently results in LoginUtils::Get()->DoBrowserLaunch() which may postpone launching browser for these reasons:
(a) There's custom user login flow defined like SupervisedUserCreationFlow. In that case login UI continues to live and custom flow UI is launched in that context.
(b) User has different app locale which requires reloading resource_bundle prior to launching browser
(c) User has custom flags set (or user flags are different from login screen flags defined by the owner), this requires restarting Chrome which will be launched in the active user session.

Small refactoring in ExistingUserController, added

* PerformPreLoginActions() - performs sets of actions right prior to login has been started.
* PerformLoginFinishedActions() - performs set of actions when login has been completed or has been cancelled.

BUG=370175

Review URL: https://codereview.chromium.org/656283002

Cr-Commit-Position: refs/heads/master@{#300708}
parent 8aecd2dd
......@@ -183,7 +183,8 @@ void KioskProfileLoader::OnOnlineChecked(
NOTREACHED();
}
void KioskProfileLoader::OnProfilePrepared(Profile* profile) {
void KioskProfileLoader::OnProfilePrepared(Profile* profile,
bool browser_launched) {
// This object could be deleted any time after successfully reporting
// a profile load, so invalidate the LoginUtils delegate now.
LoginUtils::Get()->DelegateDeleted(this);
......
......@@ -57,7 +57,8 @@ class KioskProfileLoader : public LoginPerformer::Delegate,
const std::string& email, bool success) override;
// LoginUtils::Delegate implementation:
virtual void OnProfilePrepared(Profile* profile) override;
virtual void OnProfilePrepared(Profile* profile,
bool browser_launched) override;
std::string user_id_;
bool use_guest_mount_;
......
......@@ -16,7 +16,6 @@
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "base/version.h"
......@@ -24,10 +23,8 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/boot_times_loader.h"
#include "chrome/browser/chromeos/customization_document.h"
#include "chrome/browser/chromeos/first_run/first_run.h"
#include "chrome/browser/chromeos/kiosk_mode/kiosk_mode_settings.h"
#include "chrome/browser/chromeos/login/auth/chrome_login_performer.h"
#include "chrome/browser/chromeos/login/helper.h"
......@@ -43,7 +40,6 @@
#include "chrome/browser/chromeos/policy/device_local_account_policy_service.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/ui/webui/chromeos/login/l10n_util.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
......@@ -89,11 +85,6 @@ namespace {
const char kCreateAccountURL[] =
"https://accounts.google.com/NewAccount?service=mail";
// ChromeVox tutorial URL (used in place of "getting started" url when
// accessibility is enabled).
const char kChromeVoxTutorialURLPattern[] =
"http://www.chromevox.com/tutorial/index.html?lang=%s";
// Delay for transferring the auth cache to the system profile.
const long int kAuthCacheTransferDelayMs = 2000;
......@@ -326,8 +317,7 @@ ExistingUserController::~ExistingUserController() {
void ExistingUserController::CancelPasswordChangedFlow() {
login_performer_.reset(NULL);
login_display_->SetUIEnabled(true);
StartPublicSessionAutoLoginTimer();
PerformLoginFinishedActions(true /* start public session timer */);
}
void ExistingUserController::CreateAccount() {
......@@ -344,11 +334,7 @@ void ExistingUserController::CompleteLogin(const UserContext& user_context) {
return;
}
// Stop the auto-login timer when attempting login.
StopPublicSessionAutoLoginTimer();
// Disable UI while loading user profile.
login_display_->SetUIEnabled(false);
PerformPreLoginActions(user_context);
if (!time_init_.is_null()) {
base::TimeDelta delta = base::Time::Now() - time_init_;
......@@ -387,7 +373,7 @@ void ExistingUserController::CompleteLoginInternal(
// Enable UI for the enrollment screen. SetUIEnabled(true) will post a
// request to show the sign-in screen again when invoked at the sign-in
// screen; invoke SetUIEnabled() after navigating to the enrollment screen.
login_display_->SetUIEnabled(true);
PerformLoginFinishedActions(false /* don't start public session timer */);
} else {
PerformLogin(user_context, LoginPerformer::AUTH_MODE_EXTENSION);
}
......@@ -428,34 +414,18 @@ void ExistingUserController::Login(const UserContext& user_context,
if (!user_context.HasCredentials())
return;
// Stop the auto-login timer when attempting login.
StopPublicSessionAutoLoginTimer();
// Disable clicking on other windows.
login_display_->SetUIEnabled(false);
if (last_login_attempt_username_ != user_context.GetUserID()) {
last_login_attempt_username_ = user_context.GetUserID();
num_login_attempts_ = 0;
// Also reset state variables, which are used to determine password change.
offline_failed_ = false;
online_succeeded_for_.clear();
}
num_login_attempts_++;
PerformPreLoginActions(user_context);
PerformLogin(user_context, LoginPerformer::AUTH_MODE_INTERNAL);
}
void ExistingUserController::PerformLogin(
const UserContext& user_context,
LoginPerformer::AuthorizationMode auth_mode) {
ChromeUserManager::Get()->GetUserFlow(last_login_attempt_username_)->set_host(
ChromeUserManager::Get()->GetUserFlow(user_context.GetUserID())->set_host(
host_);
BootTimesLoader::Get()->RecordLoginAttempted();
// Disable UI while loading user profile.
login_display_->SetUIEnabled(false);
// Use the same LoginPerformer for subsequent login as it has state
// such as Authenticator instance.
if (!login_performer_.get() || num_login_attempts_ <= 1) {
......@@ -464,7 +434,6 @@ void ExistingUserController::PerformLogin(
login_performer_.reset(new ChromeLoginPerformer(this));
}
is_login_in_progress_ = true;
if (gaia::ExtractDomainName(user_context.GetUserID()) ==
chromeos::login::kSupervisedUserDomain) {
login_performer_->LoginAsSupervisedUser(user_context);
......@@ -476,18 +445,15 @@ void ExistingUserController::PerformLogin(
}
void ExistingUserController::LoginAsRetailModeUser() {
// Stop the auto-login timer when attempting login.
StopPublicSessionAutoLoginTimer();
PerformPreLoginActions(UserContext(user_manager::USER_TYPE_RETAIL_MODE,
chromeos::login::kRetailModeUserName));
// Disable clicking on other windows.
login_display_->SetUIEnabled(false);
// TODO(rkc): Add a CHECK to make sure retail mode logins are allowed once
// the enterprise policy wiring is done for retail mode.
// Only one instance of LoginPerformer should exist at a time.
login_performer_.reset(NULL);
login_performer_.reset(new ChromeLoginPerformer(this));
is_login_in_progress_ = true;
login_performer_->LoginRetailMode();
SendAccessibilityAlert(
l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNIN_DEMOUSER));
......@@ -499,11 +465,8 @@ void ExistingUserController::LoginAsGuest() {
return;
}
// Stop the auto-login timer when attempting login.
StopPublicSessionAutoLoginTimer();
// Disable clicking on other windows.
login_display_->SetUIEnabled(false);
PerformPreLoginActions(UserContext(user_manager::USER_TYPE_GUEST,
chromeos::login::kGuestUserName));
CrosSettingsProvider::TrustedStatus status =
cros_settings_->PrepareTrustedValues(
......@@ -513,9 +476,7 @@ void ExistingUserController::LoginAsGuest() {
if (status == CrosSettingsProvider::PERMANENTLY_UNTRUSTED) {
login_display_->ShowError(IDS_LOGIN_ERROR_OWNER_KEY_LOST, 1,
HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
// Reenable clicking on other windows and status area.
login_display_->SetUIEnabled(true);
StartPublicSessionAutoLoginTimer();
PerformLoginFinishedActions(false /* don't start public session timer */);
display_email_.clear();
return;
} else if (status != CrosSettingsProvider::TRUSTED) {
......@@ -532,9 +493,7 @@ void ExistingUserController::LoginAsGuest() {
// this nicely.
login_display_->ShowError(IDS_LOGIN_ERROR_WHITELIST, 1,
HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
// Reenable clicking on other windows and status area.
login_display_->SetUIEnabled(true);
StartPublicSessionAutoLoginTimer();
PerformLoginFinishedActions(true /* start public session timer */);
display_email_.clear();
return;
}
......@@ -542,7 +501,6 @@ void ExistingUserController::LoginAsGuest() {
// Only one instance of LoginPerformer should exist at a time.
login_performer_.reset(NULL);
login_performer_.reset(new ChromeLoginPerformer(this));
is_login_in_progress_ = true;
login_performer_->LoginOffTheRecord();
SendAccessibilityAlert(
l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNIN_OFFRECORD));
......@@ -561,11 +519,7 @@ void ExistingUserController::LoginAsPublicSession(
return;
}
// Stop the auto-login timer when attempting login.
StopPublicSessionAutoLoginTimer();
// Disable clicking on other windows.
login_display_->SetUIEnabled(false);
PerformPreLoginActions(user_context);
CrosSettingsProvider::TrustedStatus status =
cros_settings_->PrepareTrustedValues(
......@@ -577,8 +531,7 @@ void ExistingUserController::LoginAsPublicSession(
if (status == CrosSettingsProvider::PERMANENTLY_UNTRUSTED) {
login_display_->ShowError(IDS_LOGIN_ERROR_OWNER_KEY_LOST, 1,
HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
// Re-enable clicking on other windows.
login_display_->SetUIEnabled(true);
PerformLoginFinishedActions(false /* don't start public session timer */);
return;
}
......@@ -592,9 +545,7 @@ void ExistingUserController::LoginAsPublicSession(
const user_manager::User* user =
user_manager::UserManager::Get()->FindUser(user_context.GetUserID());
if (!user || user->GetType() != user_manager::USER_TYPE_PUBLIC_ACCOUNT) {
// Re-enable clicking on other windows.
login_display_->SetUIEnabled(true);
StartPublicSessionAutoLoginTimer();
PerformLoginFinishedActions(true /* start public session timer */);
return;
}
......@@ -769,16 +720,15 @@ void ExistingUserController::ShowTPMError() {
//
void ExistingUserController::OnAuthFailure(const AuthFailure& failure) {
is_login_in_progress_ = false;
offline_failed_ = true;
guest_mode_url_ = GURL::EmptyGURL();
std::string error = failure.GetErrorString();
PerformLoginFinishedActions(false /* don't start public session timer */);
if (ChromeUserManager::Get()
->GetUserFlow(last_login_attempt_username_)
->HandleLoginFailure(failure)) {
login_display_->SetUIEnabled(true);
return;
}
......@@ -817,8 +767,6 @@ void ExistingUserController::OnAuthFailure(const AuthFailure& failure) {
ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
}
}
// Reenable clicking on other windows and status area.
login_display_->SetUIEnabled(true);
login_display_->ClearAndEnablePassword();
StartPublicSessionAutoLoginTimer();
}
......@@ -876,46 +824,20 @@ void ExistingUserController::OnAuthSuccess(const UserContext& user_context) {
}
}
void ExistingUserController::OnProfilePrepared(Profile* profile) {
void ExistingUserController::OnProfilePrepared(Profile* profile,
bool browser_launched) {
// Reenable clicking on other windows and status area.
login_display_->SetUIEnabled(true);
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
if (user_manager->IsCurrentUserNew() &&
user_manager->IsLoggedInAsSupervisedUser()) {
// Supervised users should launch into empty desktop on first run.
CommandLine::ForCurrentProcess()->AppendSwitch(::switches::kSilentLaunch);
}
if (user_manager->IsCurrentUserNew() &&
!ChromeUserManager::Get()
->GetCurrentUserFlow()
->ShouldSkipPostLoginScreens() &&
!WizardController::default_controller()->skip_post_login_screens()) {
// Don't specify start URLs if the administrator has configured the start
// URLs via policy.
if (!SessionStartupPref::TypeIsManaged(profile->GetPrefs()))
InitializeStartUrls();
// Mark the device as registered., i.e. the second part of OOBE as
// completed.
if (!StartupUtils::IsDeviceRegistered())
StartupUtils::MarkDeviceRegistered(base::Closure());
if (CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kOobeSkipPostLogin)) {
LoginUtils::Get()->DoBrowserLaunch(profile, host_);
host_ = NULL;
} else {
ActivateWizard(WizardController::kTermsOfServiceScreenName);
}
} else {
LoginUtils::Get()->DoBrowserLaunch(profile, host_);
if (browser_launched)
host_ = NULL;
}
// Inform |auth_status_consumer_| about successful login.
if (auth_status_consumer_)
auth_status_consumer_->OnAuthSuccess(UserContext());
// TODO(nkostylev): Pass UserContext back crbug.com/424550
if (auth_status_consumer_) {
auth_status_consumer_->
OnAuthSuccess(UserContext(last_login_attempt_username_));
}
}
void ExistingUserController::OnOffTheRecordAuthSuccess() {
......@@ -969,13 +891,11 @@ void ExistingUserController::OnPasswordChangeDetected() {
}
void ExistingUserController::WhiteListCheckFailed(const std::string& email) {
is_login_in_progress_ = false;
PerformLoginFinishedActions(true /* start public session timer */);
offline_failed_ = false;
ShowError(IDS_LOGIN_ERROR_WHITELIST, email);
// Reenable clicking on other windows and status area.
login_display_->SetUIEnabled(true);
login_display_->ShowSigninUI(email);
if (auth_status_consumer_) {
......@@ -984,22 +904,14 @@ void ExistingUserController::WhiteListCheckFailed(const std::string& email) {
}
display_email_.clear();
StartPublicSessionAutoLoginTimer();
}
void ExistingUserController::PolicyLoadFailed() {
ShowError(IDS_LOGIN_ERROR_OWNER_KEY_LOST, "");
// Reenable clicking on other windows and status area.
is_login_in_progress_ = false;
PerformLoginFinishedActions(false /* don't start public session timer */);
offline_failed_ = false;
login_display_->SetUIEnabled(true);
display_email_.clear();
// Policy load failure stops login attempts -- restart the timer.
StartPublicSessionAutoLoginTimer();
}
void ExistingUserController::OnOnlineChecked(const std::string& username,
......@@ -1024,11 +936,6 @@ void ExistingUserController::DeviceSettingsChanged() {
}
}
void ExistingUserController::ActivateWizard(const std::string& screen_name) {
scoped_ptr<base::DictionaryValue> params;
host_->StartWizard(screen_name, params.Pass());
}
LoginPerformer::AuthorizationMode ExistingUserController::auth_mode() const {
if (login_performer_)
return login_performer_->auth_mode();
......@@ -1123,56 +1030,6 @@ gfx::NativeWindow ExistingUserController::GetNativeWindow() const {
return host_->GetNativeWindow();
}
void ExistingUserController::InitializeStartUrls() const {
std::vector<std::string> start_urls;
const base::ListValue *urls;
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
bool can_show_getstarted_guide =
user_manager->GetActiveUser()->GetType() ==
user_manager::USER_TYPE_REGULAR &&
!user_manager->IsCurrentUserNonCryptohomeDataEphemeral();
if (user_manager->IsLoggedInAsDemoUser()) {
if (CrosSettings::Get()->GetList(kStartUpUrls, &urls)) {
// The retail mode user will get start URLs from a special policy if it is
// set.
for (base::ListValue::const_iterator it = urls->begin();
it != urls->end(); ++it) {
std::string url;
if ((*it)->GetAsString(&url))
start_urls.push_back(url);
}
}
can_show_getstarted_guide = false;
// Skip the default first-run behavior for public accounts.
} else if (!user_manager->IsLoggedInAsPublicAccount()) {
if (AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) {
const char* url = kChromeVoxTutorialURLPattern;
PrefService* prefs = g_browser_process->local_state();
const std::string current_locale =
base::StringToLowerASCII(prefs->GetString(prefs::kApplicationLocale));
std::string vox_url = base::StringPrintf(url, current_locale.c_str());
start_urls.push_back(vox_url);
can_show_getstarted_guide = false;
}
}
// Only show getting started guide for a new user.
const bool should_show_getstarted_guide = user_manager->IsCurrentUserNew();
if (can_show_getstarted_guide && should_show_getstarted_guide) {
// Don't open default Chrome window if we're going to launch the first-run
// app. Because we dont' want the first-run app to be hidden in the
// background.
CommandLine::ForCurrentProcess()->AppendSwitch(::switches::kSilentLaunch);
first_run::MaybeLaunchDialogAfterSessionStart();
} else {
for (size_t i = 0; i < start_urls.size(); ++i) {
CommandLine::ForCurrentProcess()->AppendArg(start_urls[i]);
}
}
}
void ExistingUserController::ShowError(int error_id,
const std::string& details) {
// TODO(dpolukhin): show detailed error info. |details| string contains
......@@ -1256,10 +1113,46 @@ void ExistingUserController::LoginAsPublicSessionInternal(
// Only one instance of LoginPerformer should exist at a time.
login_performer_.reset(NULL);
login_performer_.reset(new ChromeLoginPerformer(this));
is_login_in_progress_ = true;
login_performer_->LoginAsPublicSession(user_context);
SendAccessibilityAlert(
l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNIN_PUBLIC_ACCOUNT));
}
void ExistingUserController::PerformPreLoginActions(
const UserContext& user_context) {
// Disable clicking on other windows and status tray.
login_display_->SetUIEnabled(false);
if (last_login_attempt_username_ != user_context.GetUserID()) {
last_login_attempt_username_ = user_context.GetUserID();
num_login_attempts_ = 0;
// Also reset state variables, which are used to determine password change.
offline_failed_ = false;
online_succeeded_for_.clear();
}
// Guard in cases when we're called twice but login process is still active.
// This might happen when login process is paused till signed settings status
// is verified which results in Login* method called again as a callback.
if (!is_login_in_progress_)
num_login_attempts_++;
is_login_in_progress_ = true;
// Stop the auto-login timer when attempting login.
StopPublicSessionAutoLoginTimer();
}
void ExistingUserController::PerformLoginFinishedActions(
bool start_public_session_timer) {
is_login_in_progress_ = false;
// Reenable clicking on other windows and status area.
login_display_->SetUIEnabled(true);
if (start_public_session_timer)
StartPublicSessionAutoLoginTimer();
}
} // namespace chromeos
......@@ -156,20 +156,15 @@ class ExistingUserController : public LoginDisplay::Delegate,
const std::string& username, bool success) override;
// LoginUtils::Delegate implementation:
virtual void OnProfilePrepared(Profile* profile) override;
virtual void OnProfilePrepared(Profile* profile,
bool browser_launched) override;
// Called when device settings change.
void DeviceSettingsChanged();
// Starts WizardController with the specified screen.
void ActivateWizard(const std::string& screen_name);
// Returns corresponding native window.
gfx::NativeWindow GetNativeWindow() const;
// Adds first-time login URLs.
void InitializeStartUrls() const;
// Show error message. |error_id| error message ID in resources.
// If |details| string is not empty, it specify additional error text
// provided by authenticator, it is not localized.
......@@ -235,6 +230,14 @@ class ExistingUserController : public LoginDisplay::Delegate,
// preconditions have been verified.
void LoginAsPublicSessionInternal(const UserContext& user_context);
// Performs sets of actions right prior to login has been started.
void PerformPreLoginActions(const UserContext& user_context);
// Performs set of actions when login has been completed or has been
// cancelled. If |start_public_session_timer| is true than public session
// auto-login timer is started.
void PerformLoginFinishedActions(bool start_public_session_timer);
// Public session auto-login timer.
scoped_ptr<base::OneShotTimer<ExistingUserController> > auto_login_timer_;
......
......@@ -163,6 +163,9 @@ class ExistingUserControllerTest : public policy::DevicePolicyCrosBrowserTest {
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_user_manager_, Shutdown())
.Times(1);
EXPECT_CALL(*mock_user_manager_, FindUser(_))
.Times(AnyNumber())
.WillRepeatedly(ReturnNull());
}
virtual void SetUpOnMainThread() override {
......@@ -175,7 +178,8 @@ class ExistingUserControllerTest : public policy::DevicePolicyCrosBrowserTest {
profile_prepared_cb_ =
base::Bind(&ExistingUserController::OnProfilePrepared,
base::Unretained(existing_user_controller()),
testing_profile_.get());
testing_profile_.get(),
false);
}
virtual void TearDownOnMainThread() override {
......@@ -232,10 +236,8 @@ class ExistingUserControllerTest : public policy::DevicePolicyCrosBrowserTest {
};
IN_PROC_BROWSER_TEST_F(ExistingUserControllerTest, ExistingUserLogin) {
// This is disabled twice: once right after signin but before checking for
// auto-enrollment, and again after doing an ownership status check.
EXPECT_CALL(*mock_login_display_, SetUIEnabled(false))
.Times(2);
.Times(1);
UserContext user_context(kUsername);
user_context.SetKey(Key(kPassword));
user_context.SetUserIDHash(kUsername);
......@@ -246,10 +248,6 @@ IN_PROC_BROWSER_TEST_F(ExistingUserControllerTest, ExistingUserLogin) {
.Times(1)
.WillOnce(InvokeWithoutArgs(&profile_prepared_cb_,
&base::Callback<void(void)>::Run));
EXPECT_CALL(*mock_login_utils_,
DoBrowserLaunch(testing_profile_.get(),
mock_login_display_host_.get()))
.Times(1);
EXPECT_CALL(*mock_login_display_, SetUIEnabled(true))
.Times(1);
EXPECT_CALL(*mock_login_display_host_,
......@@ -294,10 +292,6 @@ IN_PROC_BROWSER_TEST_F(ExistingUserControllerTest,
StartWizardPtr(WizardController::kEnrollmentScreenName,
_))
.Times(0);
EXPECT_CALL(*mock_login_display_host_,
StartWizardPtr(WizardController::kTermsOfServiceScreenName,
NULL))
.Times(1);
UserContext user_context(kNewUsername);
user_context.SetKey(Key(kPassword));
user_context.SetUserIDHash(kNewUsername);
......@@ -327,7 +321,7 @@ IN_PROC_BROWSER_TEST_F(ExistingUserControllerTest,
// This is disabled twice: once right after signin but before checking for
// auto-enrollment, and again after doing an ownership status check.
EXPECT_CALL(*mock_login_display_, SetUIEnabled(false))
.Times(2)
.Times(1)
.InSequence(uiEnabledSequence);
EXPECT_CALL(*mock_login_display_, SetUIEnabled(true))
.Times(1)
......@@ -437,10 +431,6 @@ class ExistingUserControllerPublicSessionTest
.Times(1)
.WillOnce(InvokeWithoutArgs(&profile_prepared_cb_,
&base::Callback<void(void)>::Run));
EXPECT_CALL(*mock_login_utils_,
DoBrowserLaunch(testing_profile_.get(),
mock_login_display_host_.get()))
.Times(1);
EXPECT_CALL(*mock_login_display_, SetUIEnabled(true))
.Times(1);
EXPECT_CALL(*mock_login_display_host_,
......
......@@ -9,6 +9,7 @@
#include "base/prefs/pref_service.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
#include "chrome/browser/chromeos/login/user_flow.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/supervised_user_manager.h"
......@@ -60,11 +61,11 @@ void FakeLoginUtils::PrepareProfile(const UserContext& user_context,
bool has_cookies,
bool has_active_session,
LoginUtils::Delegate* delegate) {
user_manager::UserManager::Get()->UserLoggedIn(
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
user_manager->UserLoggedIn(
user_context.GetUserID(), user_context.GetUserIDHash(), false);
user_manager::User* user =
user_manager::UserManager::Get()->FindUserAndModify(
user_context.GetUserID());
user_manager->FindUserAndModify(user_context.GetUserID());
DCHECK(user);
// Make sure that we get the real Profile instead of the login Profile.
......@@ -73,9 +74,8 @@ void FakeLoginUtils::PrepareProfile(const UserContext& user_context,
profile->GetPrefs()->SetString(prefs::kGoogleServicesUsername,
user_context.GetUserID());
if (user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser()) {
user_manager::User* active_user =
user_manager::UserManager::Get()->GetActiveUser();
if (user_manager->IsLoggedInAsSupervisedUser()) {
user_manager::User* active_user = user_manager->GetActiveUser();
std::string supervised_user_sync_id =
ChromeUserManager::Get()->GetSupervisedUserManager()->GetUserSyncId(
active_user->email());
......@@ -89,8 +89,22 @@ void FakeLoginUtils::PrepareProfile(const UserContext& user_context,
chrome::NOTIFICATION_LOGIN_USER_PROFILE_PREPARED,
content::NotificationService::AllSources(),
content::Details<Profile>(profile));
// Emulate UserSessionManager::InitializeUserSession() for now till
// FakeLoginUtils are deprecated.
bool browser_launched = false;
if (!user_manager->IsLoggedInAsKioskApp()) {
if (user_manager->IsCurrentUserNew()) {
NOTREACHED() << "Method not implemented.";
} else {
browser_launched = true;
LoginUtils::Get()->DoBrowserLaunch(profile,
LoginDisplayHostImpl::default_host());
}
}
if (delegate)
delegate->OnProfilePrepared(profile);
delegate->OnProfilePrepared(profile, browser_launched);
}
void FakeLoginUtils::DelegateDeleted(LoginUtils::Delegate* delegate) {
......
......@@ -126,6 +126,6 @@ void LoginManagerTest::InitializeWebContents() {
EXPECT_TRUE(web_contents != NULL);
set_web_contents(web_contents);
js_checker_.set_web_contents(web_contents);
}
}
} // namespace chromeos
......@@ -48,6 +48,7 @@
#include "chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.h"
#include "chrome/browser/chromeos/login/ui/input_events_blocker.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/user_adding_screen.h"
#include "chrome/browser/chromeos/login/user_flow.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/supervised_user_manager.h"
......@@ -200,7 +201,8 @@ class LoginUtilsImpl : public LoginUtils,
bool early_restart) override;
// UserSessionManager::Delegate implementation:
virtual void OnProfilePrepared(Profile* profile) override;
virtual void OnProfilePrepared(Profile* profile,
bool browser_launched) override;
#if defined(ENABLE_RLZ)
virtual void OnRlzInitialized() override;
#endif
......@@ -371,12 +373,21 @@ void LoginUtilsImpl::PrepareProfile(
// as it coexist with SessionManager.
delegate_ = delegate;
UserSessionManager::StartSessionType start_session_type =
UserAddingScreen::Get()->IsRunning() ?
UserSessionManager::SECONDARY_USER_SESSION :
UserSessionManager::PRIMARY_USER_SESSION;
// For the transition part LoginUtils will just delegate profile
// creation and initialization to SessionManager. Later LoginUtils will be
// removed and all LoginUtils clients will just work with SessionManager
// directly.
UserSessionManager::GetInstance()->StartSession(
user_context, authenticator_, has_auth_cookies, has_active_session, this);
UserSessionManager::GetInstance()->StartSession(user_context,
start_session_type,
authenticator_,
has_auth_cookies,
has_active_session,
this);
}
void LoginUtilsImpl::DelegateDeleted(LoginUtils::Delegate* delegate) {
......@@ -429,9 +440,10 @@ scoped_refptr<Authenticator> LoginUtilsImpl::CreateAuthenticator(
return authenticator_;
}
void LoginUtilsImpl::OnProfilePrepared(Profile* profile) {
void LoginUtilsImpl::OnProfilePrepared(Profile* profile,
bool browser_launched) {
if (delegate_)
delegate_->OnProfilePrepared(profile);
delegate_->OnProfilePrepared(profile, browser_launched);
}
#if defined(ENABLE_RLZ)
......
......@@ -29,7 +29,10 @@ class LoginUtils {
class Delegate {
public:
// Called after profile is loaded and prepared for the session.
virtual void OnProfilePrepared(Profile* profile) = 0;
// |browser_launched| will be true is browser has been launched, otherwise
// it will return false and client is responsible on launching browser.
virtual void OnProfilePrepared(Profile* profile,
bool browser_launched) = 0;
#if defined(ENABLE_RLZ)
// Called after post-profile RLZ initialization.
......
......@@ -15,6 +15,7 @@
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/task_runner_util.h"
#include "base/threading/worker_pool.h"
......@@ -23,8 +24,10 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_chromeos.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/base/locale_util.h"
#include "chrome/browser/chromeos/boot_times_loader.h"
#include "chrome/browser/chromeos/first_run/first_run.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/login/chrome_restart_request.h"
#include "chrome/browser/chromeos/login/demo_mode/demo_app_launcher.h"
......@@ -35,8 +38,12 @@
#include "chrome/browser/chromeos/login/signin/oauth2_login_manager.h"
#include "chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.h"
#include "chrome/browser/chromeos/login/startup_utils.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
#include "chrome/browser/chromeos/login/user_flow.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/supervised_user_manager.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
......@@ -45,6 +52,7 @@
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/net/crl_set_fetcher.h"
#include "chrome/browser/net/nss_context.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/rlz/rlz.h"
......@@ -79,6 +87,11 @@ namespace chromeos {
namespace {
// ChromeVox tutorial URL (used in place of "getting started" url when
// accessibility is enabled).
const char kChromeVoxTutorialURLPattern[] =
"http://www.chromevox.com/tutorial/index.html?lang=%s";
void InitLocaleAndInputMethodsForNewUser(
UserSessionManager* session_manager,
Profile* profile,
......@@ -278,12 +291,14 @@ void UserSessionManager::CompleteGuestSessionLogin(const GURL& start_url) {
void UserSessionManager::StartSession(
const UserContext& user_context,
StartSessionType start_session_type,
scoped_refptr<Authenticator> authenticator,
bool has_auth_cookies,
bool has_active_session,
UserSessionManagerDelegate* delegate) {
authenticator_ = authenticator;
delegate_ = delegate;
start_session_type_ = start_session_type;
VLOG(1) << "Starting session for " << user_context.GetUserID();
......@@ -615,9 +630,8 @@ void UserSessionManager::OnConnectionTypeChanged(
}
}
void UserSessionManager::OnProfilePrepared(Profile* profile) {
LoginUtils::Get()->DoBrowserLaunch(profile, NULL); // host_, not needed here
void UserSessionManager::OnProfilePrepared(Profile* profile,
bool browser_launched) {
if (!CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestName)) {
// Did not log in (we crashed or are debugging), need to restore Sync.
// TODO(nkostylev): Make sure that OAuth state is restored correctly for all
......@@ -743,8 +757,10 @@ void UserSessionManager::InitProfilePreferences(
void UserSessionManager::UserProfileInitialized(Profile* profile,
bool is_incognito_profile,
const std::string& user_id) {
// Demo user signed in.
if (is_incognito_profile) {
profile->OnLogin();
// Send the notification before creating the browser so additional objects
// that need the profile (e.g. the launcher) can be created first.
content::NotificationService::current()->Notify(
......@@ -753,7 +769,7 @@ void UserSessionManager::UserProfileInitialized(Profile* profile,
content::Details<Profile>(profile));
if (delegate_)
delegate_->OnProfilePrepared(profile);
delegate_->OnProfilePrepared(profile, false);
return;
}
......@@ -852,6 +868,10 @@ void UserSessionManager::FinalizePrepareProfile(Profile* profile) {
UpdateEasyUnlockKeys(user_context_);
user_context_.ClearSecrets();
// Now that profile is ready, proceed to either alternative login flows or
// launch browser.
bool browser_launched = InitializeUserSession(profile);
// TODO(nkostylev): This pointer should probably never be NULL, but it looks
// like LoginUtilsImpl::OnProfileCreated() may be getting called before
// UserSessionManager::PrepareProfile() has set |delegate_| when Chrome is
......@@ -859,7 +879,103 @@ void UserSessionManager::FinalizePrepareProfile(Profile* profile) {
// this 'if' statement with a CHECK(delegate_) once the underlying issue is
// resolved.
if (delegate_)
delegate_->OnProfilePrepared(profile);
delegate_->OnProfilePrepared(profile, browser_launched);
}
void UserSessionManager::ActivateWizard(const std::string& screen_name) {
LoginDisplayHost* host = LoginDisplayHostImpl::default_host();
DCHECK(host);
if (host) {
scoped_ptr<base::DictionaryValue> params;
host->StartWizard(screen_name, params.Pass());
}
}
void UserSessionManager::InitializeStartUrls() const {
std::vector<std::string> start_urls;
const base::ListValue *urls;
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
bool can_show_getstarted_guide =
user_manager->GetActiveUser()->GetType() ==
user_manager::USER_TYPE_REGULAR &&
!user_manager->IsCurrentUserNonCryptohomeDataEphemeral();
if (user_manager->IsLoggedInAsDemoUser()) {
if (CrosSettings::Get()->GetList(kStartUpUrls, &urls)) {
// The retail mode user will get start URLs from a special policy if it is
// set.
for (base::ListValue::const_iterator it = urls->begin();
it != urls->end(); ++it) {
std::string url;
if ((*it)->GetAsString(&url))
start_urls.push_back(url);
}
}
can_show_getstarted_guide = false;
// Skip the default first-run behavior for public accounts.
} else if (!user_manager->IsLoggedInAsPublicAccount()) {
if (AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) {
const char* url = kChromeVoxTutorialURLPattern;
PrefService* prefs = g_browser_process->local_state();
const std::string current_locale =
base::StringToLowerASCII(prefs->GetString(prefs::kApplicationLocale));
std::string vox_url = base::StringPrintf(url, current_locale.c_str());
start_urls.push_back(vox_url);
can_show_getstarted_guide = false;
}
}
// Only show getting started guide for a new user.
const bool should_show_getstarted_guide = user_manager->IsCurrentUserNew();
if (can_show_getstarted_guide && should_show_getstarted_guide) {
// Don't open default Chrome window if we're going to launch the first-run
// app. Because we dont' want the first-run app to be hidden in the
// background.
CommandLine::ForCurrentProcess()->AppendSwitch(::switches::kSilentLaunch);
first_run::MaybeLaunchDialogAfterSessionStart();
} else {
for (size_t i = 0; i < start_urls.size(); ++i) {
CommandLine::ForCurrentProcess()->AppendArg(start_urls[i]);
}
}
}
bool UserSessionManager::InitializeUserSession(Profile* profile) {
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
// Kiosk apps has their own session initialization pipeline.
if (user_manager->IsLoggedInAsKioskApp())
return false;
if (start_session_type_ == PRIMARY_USER_SESSION) {
UserFlow* user_flow = ChromeUserManager::Get()->GetCurrentUserFlow();
WizardController* oobe_controller = WizardController::default_controller();
base::CommandLine* cmdline = CommandLine::ForCurrentProcess();
bool skip_post_login_screens =
user_flow->ShouldSkipPostLoginScreens() ||
(oobe_controller && oobe_controller->skip_post_login_screens()) ||
cmdline->HasSwitch(chromeos::switches::kOobeSkipPostLogin);
if (user_manager->IsCurrentUserNew() && !skip_post_login_screens) {
// Don't specify start URLs if the administrator has configured the start
// URLs via policy.
if (!SessionStartupPref::TypeIsManaged(profile->GetPrefs()))
InitializeStartUrls();
// Mark the device as registered., i.e. the second part of OOBE as
// completed.
if (!StartupUtils::IsDeviceRegistered())
StartupUtils::MarkDeviceRegistered(base::Closure());
ActivateWizard(WizardController::kTermsOfServiceScreenName);
return false;
}
}
LoginUtils::Get()->DoBrowserLaunch(profile,
LoginDisplayHostImpl::default_host());
return true;
}
void UserSessionManager::InitSessionRestoreStrategy() {
......@@ -944,7 +1060,7 @@ void UserSessionManager::InitRlzImpl(Profile* profile, bool disabled) {
}
// Init the RLZ library.
int ping_delay = profile->GetPrefs()->GetInteger(
first_run::GetPingDelayPrefName().c_str());
::first_run::GetPingDelayPrefName().c_str());
// Negative ping delay means to send ping immediately after a first search is
// recorded.
RLZTracker::InitRlzFromProfileDelayed(
......@@ -1043,10 +1159,13 @@ void UserSessionManager::RestorePendingUserSessions() {
user_context.SetIsUsingOAuth(false);
// Will call OnProfilePrepared() once profile has been loaded.
// Only handling secondary users here since primary user profile
// (and session) has been loaded on Chrome startup.
StartSession(user_context,
SECONDARY_USER_SESSION_AFTER_CRASH,
NULL, // authenticator
false, // has_auth_cookies
true, // has_active_session
true, // has_active_session, this is restart after crash
this);
} else {
RestorePendingUserSessions();
......
......@@ -41,7 +41,10 @@ class EasyUnlockKeyManager;
class UserSessionManagerDelegate {
public:
// Called after profile is loaded and prepared for the session.
virtual void OnProfilePrepared(Profile* profile) = 0;
// |browser_launched| will be true is browser has been launched, otherwise
// it will return false and client is responsible on launching browser.
virtual void OnProfilePrepared(Profile* profile,
bool browser_launched) = 0;
#if defined(ENABLE_RLZ)
// Called after post-profile RLZ initialization.
......@@ -61,10 +64,10 @@ class UserSessionStateObserver {
};
// UserSessionManager is responsible for starting user session which includes:
// load and initialize Profile (including custom Profile preferences),
// mark user as logged in and notify observers,
// initialize OAuth2 authentication session,
// initialize and launch user session based on the user type.
// * load and initialize Profile (including custom Profile preferences),
// * mark user as logged in and notify observers,
// * initialize OAuth2 authentication session,
// * initialize and launch user session based on the user type.
// Also supports restoring active user sessions after browser crash:
// load profile, restore OAuth authentication session etc.
class UserSessionManager
......@@ -74,6 +77,21 @@ class UserSessionManager
public UserSessionManagerDelegate,
public user_manager::UserManager::UserSessionStateObserver {
public:
// Context of StartSession calls.
typedef enum {
// Starting primary user session, through login UI.
PRIMARY_USER_SESSION,
// Starting secondary user session, through multi-profiles login UI.
SECONDARY_USER_SESSION,
// Starting primary user session after browser crash.
PRIMARY_USER_SESSION_AFTER_CRASH,
// Starting secondary user session after browser crash.
SECONDARY_USER_SESSION_AFTER_CRASH,
} StartSessionType;
// Returns UserSessionManager instance.
static UserSessionManager* GetInstance();
......@@ -91,6 +109,7 @@ class UserSessionManager
// Start user session given |user_context| and |authenticator| which holds
// authentication context (profile).
void StartSession(const UserContext& user_context,
StartSessionType start_session_type,
scoped_refptr<Authenticator> authenticator,
bool has_auth_cookies,
bool has_active_session,
......@@ -198,7 +217,8 @@ class UserSessionManager
// UserSessionManagerDelegate overrides:
// Used when restoring user sessions after crash.
virtual void OnProfilePrepared(Profile* profile) override;
virtual void OnProfilePrepared(Profile* profile,
bool browser_launched) override;
void CreateUserSession(const UserContext& user_context,
bool has_auth_cookies);
......@@ -233,6 +253,18 @@ class UserSessionManager
// Finalized profile preparation.
void FinalizePrepareProfile(Profile* profile);
// Starts out-of-box flow with the specified screen.
void ActivateWizard(const std::string& screen_name);
// Adds first-time login URLs.
void InitializeStartUrls() const;
// Perform session initialization and either move to additional login flows
// such as TOS (public sessions), priority pref sync UI (new users) or
// launch browser.
// Returns true if browser has been launched or false otherwise.
bool InitializeUserSession(Profile* profile);
// Initializes member variables needed for session restore process via
// OAuthLoginManager.
void InitSessionRestoreStrategy();
......@@ -270,6 +302,7 @@ class UserSessionManager
// Authentication/user context.
UserContext user_context_;
scoped_refptr<Authenticator> authenticator_;
StartSessionType start_session_type_;
// True if the authentication context's cookie jar contains authentication
// cookies from the authentication extension login flow.
......
......@@ -5,6 +5,7 @@
#include "chrome/browser/chromeos/login/supervised/supervised_user_login_flow.h"
#include "base/base64.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_registry_simple.h"
......@@ -18,6 +19,7 @@
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/supervised_user_manager.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/login/auth/key.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
......@@ -35,6 +37,14 @@ SupervisedUserLoginFlow::SupervisedUserLoginFlow(
SupervisedUserLoginFlow::~SupervisedUserLoginFlow() {}
void SupervisedUserLoginFlow::AppendAdditionalCommandLineSwitches() {
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
if (user_manager->IsCurrentUserNew()) {
// Supervised users should launch into empty desktop on first run.
CommandLine::ForCurrentProcess()->AppendSwitch(::switches::kSilentLaunch);
}
}
bool SupervisedUserLoginFlow::CanLockScreen() {
return true;
}
......@@ -59,10 +69,6 @@ bool SupervisedUserLoginFlow::HandlePasswordChangeDetected() {
return false;
}
void SupervisedUserLoginFlow::HandleOAuthTokenStatusChange(
user_manager::User::OAuthTokenStatus status) {
}
void SupervisedUserLoginFlow::OnSyncSetupDataLoaded(
const std::string& token) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
......
......@@ -24,6 +24,7 @@ class SupervisedUserLoginFlow
virtual ~SupervisedUserLoginFlow();
// ExtendedUserFlow overrides.
virtual void AppendAdditionalCommandLineSwitches() override;
virtual bool CanLockScreen() override;
virtual bool ShouldLaunchBrowser() override;
virtual bool ShouldSkipPostLoginScreens() override;
......@@ -31,8 +32,6 @@ class SupervisedUserLoginFlow
virtual bool HandleLoginFailure(const AuthFailure& failure) override;
virtual void HandleLoginSuccess(const UserContext& context) override;
virtual bool HandlePasswordChangeDetected() override;
virtual void HandleOAuthTokenStatusChange(
user_manager::User::OAuthTokenStatus status) override;
virtual void LaunchExtraSteps(Profile* profile) override;
// ExtendedAuthenticator::NewAuthStatusConsumer overrides.
......
......@@ -24,8 +24,9 @@ void TestLoginUtils::PrepareProfile(
Delegate* delegate) {
if (user_context != expected_user_context_)
NOTREACHED();
// Profile hasn't been loaded.
delegate->OnProfilePrepared(NULL);
delegate->OnProfilePrepared(NULL, false);
}
void TestLoginUtils::DelegateDeleted(Delegate* delegate) {
......
......@@ -24,6 +24,9 @@ UserFlow::~UserFlow() {}
DefaultUserFlow::~DefaultUserFlow() {}
void DefaultUserFlow::AppendAdditionalCommandLineSwitches() {
}
bool DefaultUserFlow::CanLockScreen() {
return true;
}
......@@ -68,10 +71,17 @@ ExtendedUserFlow::ExtendedUserFlow(const std::string& user_id)
ExtendedUserFlow::~ExtendedUserFlow() {
}
void ExtendedUserFlow::AppendAdditionalCommandLineSwitches() {
}
bool ExtendedUserFlow::ShouldShowSettings() {
return true;
}
void ExtendedUserFlow::HandleOAuthTokenStatusChange(
user_manager::User::OAuthTokenStatus status) {
}
void ExtendedUserFlow::UnregisterFlowSoon() {
std::string id_copy(user_id());
base::MessageLoop::current()->PostTask(FROM_HERE,
......
......@@ -21,6 +21,10 @@ class UserFlow {
public:
UserFlow();
virtual ~UserFlow() = 0;
// Provides ability to alter command line before session has started.
virtual void AppendAdditionalCommandLineSwitches() = 0;
// Indicates if screen locking should be enabled or disabled for a flow.
virtual bool CanLockScreen() = 0;
virtual bool ShouldShowSettings() = 0;
......@@ -51,6 +55,7 @@ class DefaultUserFlow : public UserFlow {
public:
virtual ~DefaultUserFlow();
virtual void AppendAdditionalCommandLineSwitches() override;
virtual bool CanLockScreen() override;
virtual bool ShouldShowSettings() override;
virtual bool ShouldLaunchBrowser() override;
......@@ -70,7 +75,10 @@ class ExtendedUserFlow : public UserFlow {
explicit ExtendedUserFlow(const std::string& user_id);
virtual ~ExtendedUserFlow();
virtual void AppendAdditionalCommandLineSwitches() override;
virtual bool ShouldShowSettings() override;
virtual void HandleOAuthTokenStatusChange(
user_manager::User::OAuthTokenStatus status) override;
protected:
// Subclasses can call this method to unregister flow in the next event.
......
......@@ -25,8 +25,9 @@ std::string CanonicalizeEmailImpl(const std::string& email_address,
char at = '@';
base::SplitString(email_address, at, &parts);
if (parts.size() != 2U) {
NOTREACHED() << "expecting exactly one @, but got " << parts.size()-1 <<
" : " << email_address;
NOTREACHED() << "expecting exactly one @, but got "
<< (parts.empty() ? 0 : parts.size() - 1)
<< " : " << email_address;
} else {
if (change_googlemail_to_gmail && parts[1] == kGooglemailDomain)
parts[1] = kGmailDomain;
......
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