blob: da86cbd7397740604a6fb399933aa3f96c74a6c5 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/ui/login_display_host_webui.h"
#include <utility>
#include <vector>
#include "ash/accessibility/focus_ring_controller.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/ash_pref_names.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/system/tray/system_tray.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
#include "chrome/browser/chromeos/base/locale_util.h"
#include "chrome/browser/chromeos/boot_times_recorder.h"
#include "chrome/browser/chromeos/first_run/drive_first_run_controller.h"
#include "chrome/browser/chromeos/first_run/first_run.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/browser/chromeos/login/existing_user_controller.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/login_wizard.h"
#include "chrome/browser/chromeos/login/screens/core_oobe_view.h"
#include "chrome/browser/chromeos/login/screens/gaia_view.h"
#include "chrome/browser/chromeos/login/startup_utils.h"
#include "chrome/browser/chromeos/login/ui/input_events_blocker.h"
#include "chrome/browser/chromeos/login/ui/login_display_host_mojo.h"
#include "chrome/browser/chromeos/login/ui/login_display_webui.h"
#include "chrome/browser/chromeos/login/ui/webui_login_view.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/chromeos/net/delay_network_call.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/enrollment_config.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/system/input_device_settings.h"
#include "chrome/browser/chromeos/system/timezone_resolver_manager.h"
#include "chrome/browser/chromeos/system/timezone_util.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/system_tray_client.h"
#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/browser_resources.h"
#include "chromeos/audio/chromeos_sounds.h"
#include "chromeos/chromeos_constants.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/session_manager_client.h"
#include "chromeos/login/login_state.h"
#include "chromeos/settings/cros_settings_names.h"
#include "chromeos/settings/cros_settings_provider.h"
#include "chromeos/settings/timezone_settings.h"
#include "chromeos/timezone/timezone_resolver.h"
#include "components/account_id/account_id.h"
#include "components/language/core/common/locale_util.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "media/audio/sounds/sounds_manager.h"
#include "services/ui/public/cpp/property_type_converters.h"
#include "services/ui/public/interfaces/window_manager.mojom.h"
#include "ui/aura/window.h"
#include "ui/base/ime/chromeos/extension_ime_util.h"
#include "ui/base/ime/chromeos/input_method_manager.h"
#include "ui/base/ime/chromeos/input_method_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/devices/input_device_manager.h"
#include "ui/events/event_handler.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/transform.h"
#include "ui/keyboard/keyboard_controller.h"
#include "ui/keyboard/keyboard_util.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "url/gurl.h"
namespace {
// Maximum delay for startup sound after 'loginPromptVisible' signal.
const int kStartupSoundMaxDelayMs = 2000;
// URL which corresponds to the login WebUI.
const char kLoginURL[] = "chrome://oobe/login";
// URL which corresponds to the OOBE WebUI.
const char kOobeURL[] = "chrome://oobe/oobe";
// URL which corresponds to the user adding WebUI.
const char kUserAddingURL[] = "chrome://oobe/user-adding";
// URL which corresponds to the app launch splash WebUI.
const char kAppLaunchSplashURL[] = "chrome://oobe/app-launch-splash";
// URL which corresponds to the ARC kiosk splash WebUI.
const char kArcKioskSplashURL[] = "chrome://oobe/arc-kiosk-splash";
// Duration of sign-in transition animation.
const int kLoginFadeoutTransitionDurationMs = 700;
// Number of times we try to reload OOBE/login WebUI if it crashes.
const int kCrashCountLimit = 5;
// The default fade out animation time in ms.
const int kDefaultFadeTimeMs = 200;
// Whether to enable tnitializing WebUI in hidden state (see
// |initialize_webui_hidden_|) by default.
const bool kHiddenWebUIInitializationDefault = true;
// Switch values that might be used to override WebUI init type.
const char kWebUIInitParallel[] = "parallel";
const char kWebUIInitPostpone[] = "postpone";
// A class to observe an implicit animation and invokes the callback after the
// animation is completed.
class AnimationObserver : public ui::ImplicitAnimationObserver {
public:
explicit AnimationObserver(const base::Closure& callback)
: callback_(callback) {}
~AnimationObserver() override {}
private:
// ui::ImplicitAnimationObserver implementation:
void OnImplicitAnimationsCompleted() override {
callback_.Run();
delete this;
}
base::Closure callback_;
DISALLOW_COPY_AND_ASSIGN(AnimationObserver);
};
// Even if oobe is complete we may still want to show it, for example, if there
// are no users registered then the user may want to enterprise enroll.
bool IsOobeComplete() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
// Oobe is completed and we have a user or we are enterprise enrolled.
return chromeos::StartupUtils::IsOobeCompleted() &&
(!user_manager::UserManager::Get()->GetUsers().empty() ||
connector->IsEnterpriseManaged());
}
// Returns true if signin (not oobe) should be displayed.
bool ShouldShowSigninScreen(chromeos::OobeScreen first_screen) {
return (first_screen == chromeos::OobeScreen::SCREEN_UNKNOWN &&
IsOobeComplete()) ||
first_screen == chromeos::OobeScreen::SCREEN_SPECIAL_LOGIN;
}
// ShowLoginWizard is split into two parts. This function is sometimes called
// from TriggerShowLoginWizardFinish() directly, and sometimes from
// OnLanguageSwitchedCallback()
// (if locale was updated).
void ShowLoginWizardFinish(
chromeos::OobeScreen first_screen,
const chromeos::StartupCustomizationDocument* startup_manifest) {
TRACE_EVENT0("chromeos", "ShowLoginWizard::ShowLoginWizardFinish");
// TODO(crbug.com/781402): Move LoginDisplayHost creation out of
// LoginDisplayHostWebUI, it is not specific to a particular implementation.
// Create the LoginDisplayHost. Use the views-based implementation only for
// the sign-in screen.
chromeos::LoginDisplayHost* display_host = nullptr;
if (chromeos::LoginDisplayHost::default_host()) {
// Tests may have already allocated an instance for us to use.
display_host = chromeos::LoginDisplayHost::default_host();
} else if (ash::features::IsViewsLoginEnabled() &&
ShouldShowSigninScreen(first_screen)) {
display_host = new chromeos::LoginDisplayHostMojo();
} else {
display_host = new chromeos::LoginDisplayHostWebUI();
}
// Restore system timezone.
std::string timezone;
if (chromeos::system::PerUserTimezoneEnabled()) {
timezone = g_browser_process->local_state()->GetString(
prefs::kSigninScreenTimezone);
}
if (ShouldShowSigninScreen(first_screen)) {
display_host->StartSignInScreen(chromeos::LoginScreenContext());
} else {
display_host->StartWizard(first_screen);
// Set initial timezone if specified by customization.
const std::string customization_timezone =
startup_manifest->initial_timezone();
VLOG(1) << "Initial time zone: " << customization_timezone;
// Apply locale customizations only once to preserve whatever locale
// user has changed to during OOBE.
if (!customization_timezone.empty())
timezone = customization_timezone;
}
if (!timezone.empty()) {
chromeos::system::SetSystemAndSigninScreenTimezone(timezone);
}
}
struct ShowLoginWizardSwitchLanguageCallbackData {
explicit ShowLoginWizardSwitchLanguageCallbackData(
chromeos::OobeScreen first_screen,
const chromeos::StartupCustomizationDocument* startup_manifest)
: first_screen(first_screen), startup_manifest(startup_manifest) {}
const chromeos::OobeScreen first_screen;
const chromeos::StartupCustomizationDocument* const startup_manifest;
// lock UI while resource bundle is being reloaded.
chromeos::InputEventsBlocker events_blocker;
};
void OnLanguageSwitchedCallback(
std::unique_ptr<ShowLoginWizardSwitchLanguageCallbackData> self,
const chromeos::locale_util::LanguageSwitchResult& result) {
if (!result.success)
LOG(WARNING) << "Locale could not be found for '" << result.requested_locale
<< "'";
ShowLoginWizardFinish(self->first_screen, self->startup_manifest);
}
// Triggers ShowLoginWizardFinish directly if no locale switch is required
// (|switch_locale| is empty) or after a locale switch otherwise.
void TriggerShowLoginWizardFinish(
std::string switch_locale,
std::unique_ptr<ShowLoginWizardSwitchLanguageCallbackData> data) {
if (switch_locale.empty()) {
ShowLoginWizardFinish(data->first_screen, data->startup_manifest);
} else {
chromeos::locale_util::SwitchLanguageCallback callback(
base::Bind(&OnLanguageSwitchedCallback, base::Passed(std::move(data))));
// Load locale keyboards here. Hardware layout would be automatically
// enabled.
chromeos::locale_util::SwitchLanguage(
switch_locale, true, true /* login_layouts_only */, callback,
ProfileManager::GetActiveUserProfile());
}
}
// Returns the login screen locale mandated by device policy, or an empty string
// if no policy-specified locale is set.
std::string GetManagedLoginScreenLocale() {
chromeos::CrosSettings* cros_settings = chromeos::CrosSettings::Get();
const base::ListValue* login_screen_locales = nullptr;
if (!cros_settings->GetList(chromeos::kDeviceLoginScreenLocales,
&login_screen_locales))
return std::string();
// Currently, only the first element is used. The setting is a list for future
// compatibility, if dynamically switching locales on the login screen will be
// implemented.
std::string login_screen_locale;
if (login_screen_locales->empty() ||
!login_screen_locales->GetString(0, &login_screen_locale))
return std::string();
return login_screen_locale;
}
// Disables virtual keyboard overscroll. Login UI will scroll user pods
// into view on JS side when virtual keyboard is shown.
void DisableKeyboardOverscroll() {
keyboard::SetKeyboardOverscrollOverride(
keyboard::KEYBOARD_OVERSCROLL_OVERRIDE_DISABLED);
}
void ResetKeyboardOverscrollOverride() {
keyboard::SetKeyboardOverscrollOverride(
keyboard::KEYBOARD_OVERSCROLL_OVERRIDE_NONE);
}
class CloseAfterCommit : public ui::CompositorObserver,
public views::WidgetObserver {
public:
explicit CloseAfterCommit(views::Widget* widget) : widget_(widget) {
widget->GetCompositor()->AddObserver(this);
widget_->AddObserver(this);
}
~CloseAfterCommit() override {
widget_->RemoveObserver(this);
widget_->GetCompositor()->RemoveObserver(this);
}
// ui::CompositorObserver:
void OnCompositingDidCommit(ui::Compositor* compositor) override {
DCHECK_EQ(widget_->GetCompositor(), compositor);
widget_->Close();
}
void OnCompositingStarted(ui::Compositor* compositor,
base::TimeTicks start_time) override {}
void OnCompositingEnded(ui::Compositor* compositor) override {}
void OnCompositingLockStateChanged(ui::Compositor* compositor) override {}
void OnCompositingChildResizing(ui::Compositor* compositor) override {}
void OnCompositingShuttingDown(ui::Compositor* compositor) override {}
// views::WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override {
DCHECK_EQ(widget, widget_);
delete this;
}
private:
views::Widget* const widget_;
DISALLOW_COPY_AND_ASSIGN(CloseAfterCommit);
};
} // namespace
namespace chromeos {
// static
const int LoginDisplayHostWebUI::kShowLoginWebUIid = 0x1111;
// A class to handle special menu key for keyboard driven OOBE.
class LoginDisplayHostWebUI::KeyboardDrivenOobeKeyHandler
: public ui::EventHandler {
public:
KeyboardDrivenOobeKeyHandler() {
ash::Shell::Get()->AddPreTargetHandler(this);
}
~KeyboardDrivenOobeKeyHandler() override {
ash::Shell::Get()->RemovePreTargetHandler(this);
}
private:
// ui::EventHandler
void OnKeyEvent(ui::KeyEvent* event) override {
if (event->key_code() == ui::VKEY_F6) {
SystemTrayClient::Get()->SetPrimaryTrayVisible(false);
event->StopPropagation();
}
}
DISALLOW_COPY_AND_ASSIGN(KeyboardDrivenOobeKeyHandler);
};
// A login implementation of WidgetDelegate.
class LoginDisplayHostWebUI::LoginWidgetDelegate
: public views::WidgetDelegate {
public:
LoginWidgetDelegate(views::Widget* widget, LoginDisplayHostWebUI* host)
: widget_(widget), login_display_host_(host) {
DCHECK(widget_);
DCHECK(login_display_host_);
}
~LoginWidgetDelegate() override {}
void LoginDisplayHostDestroyed() { login_display_host_ = nullptr; }
// Overridden from WidgetDelegate:
void WindowClosing() override {
// Reset the cached Widget and View pointers. The Widget may close due to:
// * Login completion
// * Ash crash at the login screen on mustash
// In the latter case the mash root process will trigger a clean restart
// of content_browser.
if (!features::IsAshInBrowserProcess() && login_display_host_)
login_display_host_->ResetLoginWindowAndView();
}
void DeleteDelegate() override { delete this; }
views::Widget* GetWidget() override { return widget_; }
const views::Widget* GetWidget() const override { return widget_; }
bool CanActivate() const override { return true; }
bool ShouldAdvanceFocusToTopLevelWidget() const override { return true; }
private:
views::Widget* widget_;
// Set to null if LoginDisplayHostWebUI is destroyed before us.
LoginDisplayHostWebUI* login_display_host_;
DISALLOW_COPY_AND_ASSIGN(LoginWidgetDelegate);
};
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, public
LoginDisplayHostWebUI::LoginDisplayHostWebUI()
: oobe_startup_sound_played_(StartupUtils::IsOobeCompleted()),
weak_factory_(this) {
if (!features::IsAshInBrowserProcess()) {
// Animation, and initializing hidden, are not currently supported for Mash.
finalize_animation_type_ = ANIMATION_NONE;
initialize_webui_hidden_ = false;
}
DBusThreadManager::Get()->GetSessionManagerClient()->AddObserver(this);
CrasAudioHandler::Get()->AddAudioObserver(this);
display::Screen::GetScreen()->AddObserver(this);
ui::InputDeviceManager::GetInstance()->AddObserver(this);
// Login screen is moved to lock screen container when user logs in.
registrar_.Add(this, chrome::NOTIFICATION_LOGIN_USER_CHANGED,
content::NotificationService::AllSources());
bool zero_delay_enabled = WizardController::IsZeroDelayEnabled();
// Mash always runs login screen with zero delay
if (!features::IsAshInBrowserProcess())
zero_delay_enabled = true;
waiting_for_wallpaper_load_ = !zero_delay_enabled;
// Initializing hidden is not supported in Mash
if (features::IsAshInBrowserProcess()) {
initialize_webui_hidden_ =
kHiddenWebUIInitializationDefault && !zero_delay_enabled;
}
// Check if WebUI init type is overriden. Not supported in Mash.
if (features::IsAshInBrowserProcess() &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAshWebUIInit)) {
const std::string override_type =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAshWebUIInit);
if (override_type == kWebUIInitParallel)
initialize_webui_hidden_ = true;
else if (override_type == kWebUIInitPostpone)
initialize_webui_hidden_ = false;
}
// Always postpone WebUI initialization on first boot, otherwise we miss
// initial animation.
if (!StartupUtils::IsOobeCompleted())
initialize_webui_hidden_ = false;
if (waiting_for_wallpaper_load_) {
registrar_.Add(this, chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
content::NotificationService::AllSources());
}
// When we wait for WebUI to be initialized we wait for one of
// these notifications.
if (waiting_for_wallpaper_load_ && initialize_webui_hidden_) {
registrar_.Add(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
content::NotificationService::AllSources());
registrar_.Add(this, chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
content::NotificationService::AllSources());
}
VLOG(1) << "Login WebUI >> "
<< "zero_delay: " << zero_delay_enabled
<< " wait_for_wp_load_: " << waiting_for_wallpaper_load_
<< " init_webui_hidden_: " << initialize_webui_hidden_;
media::SoundsManager* manager = media::SoundsManager::Get();
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
manager->Initialize(SOUND_STARTUP,
bundle.GetRawDataResource(IDR_SOUND_STARTUP_WAV));
login_display_ = std::make_unique<LoginDisplayWebUI>();
}
LoginDisplayHostWebUI::~LoginDisplayHostWebUI() {
DBusThreadManager::Get()->GetSessionManagerClient()->RemoveObserver(this);
CrasAudioHandler::Get()->RemoveAudioObserver(this);
display::Screen::GetScreen()->RemoveObserver(this);
ui::InputDeviceManager::GetInstance()->RemoveObserver(this);
if (login_view_ && login_window_)
login_window_->RemoveRemovalsObserver(this);
if (login_window_delegate_)
login_window_delegate_->LoginDisplayHostDestroyed();
MultiUserWindowManager* window_manager =
MultiUserWindowManager::GetInstance();
// MultiUserWindowManager instance might be null if no user is logged in - or
// in a unit test.
if (window_manager)
window_manager->RemoveObserver(this);
ResetKeyboardOverscrollOverride();
views::FocusManager::set_arrow_key_traversal_enabled(false);
ResetLoginWindowAndView();
// TODO(tengs): This should be refactored. See crbug.com/314934.
if (user_manager::UserManager::Get()->IsCurrentUserNew()) {
// DriveOptInController will delete itself when finished.
(new DriveFirstRunController(ProfileManager::GetActiveUserProfile()))
->EnableOfflineMode();
}
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, LoginDisplayHost:
LoginDisplay* LoginDisplayHostWebUI::GetLoginDisplay() {
return login_display_.get();
}
gfx::NativeWindow LoginDisplayHostWebUI::GetNativeWindow() const {
return login_window_ ? login_window_->GetNativeWindow() : nullptr;
}
WebUILoginView* LoginDisplayHostWebUI::GetWebUILoginView() const {
return login_view_;
}
void LoginDisplayHostWebUI::OnFinalize() {
DVLOG(1) << "Finalizing LoginDisplayHost. User session starting";
switch (finalize_animation_type_) {
case ANIMATION_NONE:
ShutdownDisplayHost();
break;
case ANIMATION_WORKSPACE:
if (ash::Shell::HasInstance())
ScheduleWorkspaceAnimation();
ShutdownDisplayHost();
break;
case ANIMATION_FADE_OUT:
// Display host is deleted once animation is completed
// since sign in screen widget has to stay alive.
ScheduleFadeOutAnimation(kDefaultFadeTimeMs);
break;
case ANIMATION_ADD_USER:
// Defer the deletion of LoginDisplayHost instance until the user adding
// animation (which is done by UserSwitchAnimatorChromeOS) is finished.
// This is to guarantee OnUserSwitchAnimationFinished() is called before
// LoginDisplayHost deletes itself.
// See crbug.com/541864.
break;
default:
break;
}
}
void LoginDisplayHostWebUI::SetStatusAreaVisible(bool visible) {
if (initialize_webui_hidden_)
status_area_saved_visibility_ = visible;
else if (login_view_)
login_view_->SetStatusAreaVisible(visible);
}
void LoginDisplayHostWebUI::StartWizard(OobeScreen first_screen) {
DisableKeyboardOverscroll();
TryToPlayOobeStartupSound();
// Keep parameters to restore if renderer crashes.
restore_path_ = RESTORE_WIZARD;
first_screen_ = first_screen;
is_showing_login_ = false;
if (waiting_for_wallpaper_load_ && !initialize_webui_hidden_) {
VLOG(1) << "Login WebUI >> wizard postponed";
return;
}
VLOG(1) << "Login WebUI >> wizard";
if (!login_window_)
LoadURL(GURL(kOobeURL));
DVLOG(1) << "Starting wizard, first_screen: "
<< GetOobeScreenName(first_screen);
// Create and show the wizard.
// Note, dtor of the old WizardController should be called before ctor of the
// new one, because "default_controller()" is updated there. So pure "reset()"
// is done before new controller creation.
wizard_controller_.reset();
wizard_controller_.reset(CreateWizardController());
oobe_progress_bar_visible_ = !StartupUtils::IsDeviceRegistered();
SetOobeProgressBarVisible(oobe_progress_bar_visible_);
wizard_controller_->Init(first_screen);
}
WizardController* LoginDisplayHostWebUI::GetWizardController() {
return wizard_controller_.get();
}
void LoginDisplayHostWebUI::OnStartUserAdding() {
DisableKeyboardOverscroll();
restore_path_ = RESTORE_ADD_USER_INTO_SESSION;
// Animation is not supported in Mash
if (features::IsAshInBrowserProcess())
finalize_animation_type_ = ANIMATION_ADD_USER;
// Observe the user switch animation and defer the deletion of itself only
// after the animation is finished.
MultiUserWindowManager* window_manager =
MultiUserWindowManager::GetInstance();
// MultiUserWindowManager instance might be nullptr in a unit test.
if (window_manager)
window_manager->AddObserver(this);
VLOG(1) << "Login WebUI >> user adding";
if (!login_window_)
LoadURL(GURL(kUserAddingURL));
// We should emit this signal only at login screen (after reboot or sign out).
login_view_->set_should_emit_login_prompt_visible(false);
if (features::IsAshInBrowserProcess()) {
// Lock container can be transparent after lock screen animation.
aura::Window* lock_container = ash::Shell::GetContainer(
ash::Shell::GetPrimaryRootWindow(),
ash::kShellWindowId_LockScreenContainersContainer);
lock_container->layer()->SetOpacity(1.0);
} else {
NOTIMPLEMENTED();
}
CreateExistingUserController();
if (!signin_screen_controller_.get()) {
signin_screen_controller_.reset(new SignInScreenController(GetOobeUI()));
}
SetOobeProgressBarVisible(oobe_progress_bar_visible_ = false);
SetStatusAreaVisible(true);
existing_user_controller_->Init(
user_manager::UserManager::Get()->GetUsersAllowedForMultiProfile());
CHECK(login_display_);
GetOobeUI()->ShowSigninScreen(LoginScreenContext(), login_display_.get(),
login_display_.get());
}
void LoginDisplayHostWebUI::CancelUserAdding() {
// ANIMATION_ADD_USER observes UserSwitchAnimatorChromeOS to shutdown the
// login display host. However, the animation does not run when user adding is
// canceled. Changing to ANIMATION_NONE so that Finalize() shuts down the host
// immediately.
finalize_animation_type_ = ANIMATION_NONE;
Finalize(base::OnceClosure());
}
void LoginDisplayHostWebUI::OnStartSignInScreen(
const LoginScreenContext& context) {
DisableKeyboardOverscroll();
restore_path_ = RESTORE_SIGN_IN;
is_showing_login_ = true;
// Animation is not supported in Mash
if (features::IsAshInBrowserProcess())
finalize_animation_type_ = ANIMATION_WORKSPACE;
if (waiting_for_wallpaper_load_ && !initialize_webui_hidden_) {
VLOG(1) << "Login WebUI >> sign in postponed";
return;
}
VLOG(1) << "Login WebUI >> sign in";
// TODO(crbug.com/784495): Make sure this is ported to views.
if (!login_window_) {
TRACE_EVENT_ASYNC_BEGIN0("ui", "ShowLoginWebUI", kShowLoginWebUIid);
TRACE_EVENT_ASYNC_STEP_INTO0("ui", "ShowLoginWebUI", kShowLoginWebUIid,
"StartSignInScreen");
BootTimesRecorder::Get()->RecordCurrentStats("login-start-signin-screen");
LoadURL(GURL(kLoginURL));
}
DVLOG(1) << "Starting sign in screen";
CreateExistingUserController();
if (!signin_screen_controller_.get()) {
signin_screen_controller_.reset(new SignInScreenController(GetOobeUI()));
}
// TODO(crbug.com/784495): This is always false, since
// LoginDisplayHost::StartSignInScreen marks the device as registered.
oobe_progress_bar_visible_ = !StartupUtils::IsDeviceRegistered();
SetOobeProgressBarVisible(oobe_progress_bar_visible_);
existing_user_controller_->Init(user_manager::UserManager::Get()->GetUsers());
CHECK(login_display_);
GetOobeUI()->ShowSigninScreen(context, login_display_.get(),
login_display_.get());
TRACE_EVENT_ASYNC_STEP_INTO0("ui", "ShowLoginWebUI", kShowLoginWebUIid,
"WaitForScreenStateInitialize");
// TODO(crbug.com/784495): Make sure this is ported to views.
BootTimesRecorder::Get()->RecordCurrentStats(
"login-wait-for-signin-state-initialize");
}
void LoginDisplayHostWebUI::OnPreferencesChanged() {
if (is_showing_login_)
login_display_->OnPreferencesChanged();
}
void LoginDisplayHostWebUI::OnStartAppLaunch() {
// Animation is not supported in Mash.
if (features::IsAshInBrowserProcess())
finalize_animation_type_ = ANIMATION_FADE_OUT;
if (!login_window_)
LoadURL(GURL(kAppLaunchSplashURL));
login_view_->set_should_emit_login_prompt_visible(false);
}
void LoginDisplayHostWebUI::OnStartArcKiosk() {
// Animation is not supported in Mash.
if (features::IsAshInBrowserProcess())
finalize_animation_type_ = ANIMATION_FADE_OUT;
if (!login_window_) {
LoadURL(GURL(kAppLaunchSplashURL));
LoadURL(GURL(kArcKioskSplashURL));
}
login_view_->set_should_emit_login_prompt_visible(false);
}
bool LoginDisplayHostWebUI::IsVoiceInteractionOobe() {
return is_voice_interaction_oobe_;
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, public
WizardController* LoginDisplayHostWebUI::CreateWizardController() {
// TODO(altimofeev): ensure that WebUI is ready.
OobeUI* oobe_ui = GetOobeUI();
return new WizardController(this, oobe_ui);
}
void LoginDisplayHostWebUI::OnBrowserCreated() {
// Close lock window now so that the launched browser can receive focus.
ResetLoginWindowAndView();
}
OobeUI* LoginDisplayHostWebUI::GetOobeUI() const {
if (!login_view_)
return nullptr;
return login_view_->GetOobeUI();
}
content::WebContents* LoginDisplayHostWebUI::GetOobeWebContents() const {
if (!login_view_)
return nullptr;
return login_view_->GetWebContents();
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, content:NotificationObserver:
void LoginDisplayHostWebUI::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
LoginDisplayHostCommon::Observe(type, source, details);
if (chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE == type ||
chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN == type) {
VLOG(1) << "Login WebUI >> WEBUI_VISIBLE";
if (waiting_for_wallpaper_load_ && initialize_webui_hidden_) {
// Reduce time till login UI is shown - show it as soon as possible.
waiting_for_wallpaper_load_ = false;
ShowWebUI();
}
registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
content::NotificationService::AllSources());
registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
content::NotificationService::AllSources());
} else if (type == chrome::NOTIFICATION_LOGIN_USER_CHANGED &&
user_manager::UserManager::Get()->IsCurrentUserNew()) {
registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_USER_CHANGED,
content::NotificationService::AllSources());
} else if (chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED == type) {
VLOG(1) << "Login WebUI >> wp animation done";
is_wallpaper_loaded_ = true;
if (waiting_for_wallpaper_load_) {
// StartWizard / StartSignInScreen could be called multiple times through
// the lifetime of host.
// Make sure that subsequent calls are not postponed.
waiting_for_wallpaper_load_ = false;
if (initialize_webui_hidden_) {
// If we're in the process of switching locale, the wallpaper might
// have finished loading before the locale switch was completed.
// Only show the UI if it already exists.
if (login_window_ && login_view_)
ShowWebUI();
} else {
StartPostponedWebUI();
}
}
registrar_.Remove(this, chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
content::NotificationService::AllSources());
}
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, WebContentsObserver:
void LoginDisplayHostWebUI::RenderProcessGone(base::TerminationStatus status) {
// Do not try to restore on shutdown
if (browser_shutdown::GetShutdownType() != browser_shutdown::NOT_VALID)
return;
crash_count_++;
if (crash_count_ > kCrashCountLimit)
return;
if (status != base::TERMINATION_STATUS_NORMAL_TERMINATION) {
// Render with login screen crashed. Let's crash browser process to let
// session manager restart it properly. It is hard to reload the page
// and get to controlled state that is fully functional.
// If you see check, search for renderer crash for the same client.
LOG(FATAL) << "Renderer crash on login window";
}
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, chromeos::SessionManagerClient::Observer:
void LoginDisplayHostWebUI::EmitLoginPromptVisibleCalled() {
OnLoginPromptVisible();
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, chromeos::CrasAudioHandler::AudioObserver:
void LoginDisplayHostWebUI::OnActiveOutputNodeChanged() {
TryToPlayOobeStartupSound();
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, display::DisplayObserver:
void LoginDisplayHostWebUI::OnDisplayAdded(
const display::Display& new_display) {
if (GetOobeUI())
GetOobeUI()->OnDisplayConfigurationChanged();
}
void LoginDisplayHostWebUI::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
const display::Display primary_display =
display::Screen::GetScreen()->GetPrimaryDisplay();
if (display.id() != primary_display.id() ||
!(changed_metrics & DISPLAY_METRIC_BOUNDS)) {
return;
}
if (GetOobeUI()) {
// Reset widget size for voice interaction OOBE, since the screen rotation
// will break the widget size if it is not full screen.
if (is_voice_interaction_oobe_)
login_window_->SetSize(primary_display.work_area_size());
const gfx::Size& size = primary_display.size();
GetOobeUI()->GetCoreOobeView()->SetClientAreaSize(size.width(),
size.height());
if (changed_metrics & DISPLAY_METRIC_PRIMARY)
GetOobeUI()->OnDisplayConfigurationChanged();
}
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, ui::InputDeviceEventObserver
void LoginDisplayHostWebUI::OnTouchscreenDeviceConfigurationChanged() {
if (GetOobeUI())
GetOobeUI()->OnDisplayConfigurationChanged();
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, views::WidgetRemovalsObserver:
void LoginDisplayHostWebUI::OnWillRemoveView(views::Widget* widget,
views::View* view) {
if (view != static_cast<views::View*>(login_view_))
return;
login_view_ = nullptr;
widget->RemoveRemovalsObserver(this);
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, MultiUserWindowManager::Observer:
void LoginDisplayHostWebUI::OnUserSwitchAnimationFinished() {
ShutdownDisplayHost();
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, private
void LoginDisplayHostWebUI::ScheduleWorkspaceAnimation() {
if (!features::IsAshInBrowserProcess()) {
NOTIMPLEMENTED();
return;
}
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableLoginAnimations)) {
ash::Shell::Get()->DoInitialWorkspaceAnimation();
}
}
void LoginDisplayHostWebUI::ScheduleFadeOutAnimation(int animation_speed_ms) {
// login window might have been closed by OnBrowserCreated() at this moment.
// This may happen when adding another user into the session, and a browser
// is created before session start, which triggers the close of the login
// window. In this case, we should shut down the display host directly.
if (!login_window_) {
ShutdownDisplayHost();
return;
}
ui::Layer* layer = login_window_->GetLayer();
ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
animation.AddObserver(new AnimationObserver(
base::Bind(&LoginDisplayHostWebUI::ShutdownDisplayHost,
weak_factory_.GetWeakPtr())));
animation.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(animation_speed_ms));
layer->SetOpacity(0);
}
void LoginDisplayHostWebUI::LoadURL(const GURL& url) {
InitLoginWindowAndView();
// Subscribe to crash events.
content::WebContentsObserver::Observe(login_view_->GetWebContents());
login_view_->LoadURL(url);
}
void LoginDisplayHostWebUI::ShowWebUI() {
if (!login_window_ || !login_view_) {
NOTREACHED();
return;
}
VLOG(1) << "Login WebUI >> Show already initialized UI";
login_window_->Show();
login_view_->GetWebContents()->Focus();
login_view_->SetStatusAreaVisible(status_area_saved_visibility_);
login_view_->OnPostponedShow();
// We should reset this flag to allow changing of status area visibility.
initialize_webui_hidden_ = false;
}
void LoginDisplayHostWebUI::StartPostponedWebUI() {
if (!is_wallpaper_loaded_) {
NOTREACHED();
return;
}
VLOG(1) << "Login WebUI >> Init postponed WebUI";
// Wallpaper has finished loading before StartWizard/StartSignInScreen has
// been called. In general this should not happen.
// Let go through normal code path when one of those will be called.
if (restore_path_ == RESTORE_UNKNOWN) {
NOTREACHED();
return;
}
switch (restore_path_) {
case RESTORE_WIZARD:
StartWizard(first_screen_);
break;
case RESTORE_SIGN_IN:
StartSignInScreen(LoginScreenContext());
break;
case RESTORE_ADD_USER_INTO_SESSION:
StartUserAdding(base::OnceClosure());
break;
default:
NOTREACHED();
break;
}
}
void LoginDisplayHostWebUI::InitLoginWindowAndView() {
if (login_window_)
return;
if (system::InputDeviceSettings::Get()->ForceKeyboardDrivenUINavigation()) {
views::FocusManager::set_arrow_key_traversal_enabled(true);
// crbug.com/405859
focus_ring_controller_ = std::make_unique<ash::FocusRingController>();
focus_ring_controller_->SetVisible(true);
keyboard_driven_oobe_key_handler_.reset(new KeyboardDrivenOobeKeyHandler);
}
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = CalculateScreenBounds(gfx::Size());
// Disable fullscreen state for voice interaction OOBE since the shelf should
// be visible.
if (!is_voice_interaction_oobe_)
params.show_state = ui::SHOW_STATE_FULLSCREEN;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
// Put the voice interaction oobe inside AlwaysOnTop container instead of
// LockScreenContainer.
ash::ShellWindowId container = is_voice_interaction_oobe_
? ash::kShellWindowId_AlwaysOnTopContainer
: ash::kShellWindowId_LockScreenContainer;
// The ash::Shell containers are not available in Mash
if (features::IsAshInBrowserProcess()) {
params.parent =
ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(), container);
} else {
using ui::mojom::WindowManager;
params.mus_properties[WindowManager::kContainerId_InitProperty] =
mojo::ConvertTo<std::vector<uint8_t>>(static_cast<int32_t>(container));
}
login_window_ = new views::Widget;
params.delegate = login_window_delegate_ =
new LoginWidgetDelegate(login_window_, this);
login_window_->Init(params);
login_view_ = new WebUILoginView(WebUILoginView::WebViewSettings());
login_view_->Init();
if (login_view_->webui_visible())
OnLoginPromptVisible();
// Animations are not available in Mash.
// For voice interaction OOBE, we do not want the animation here.
if (features::IsAshInBrowserProcess() && !is_voice_interaction_oobe_) {
login_window_->SetVisibilityAnimationDuration(
base::TimeDelta::FromMilliseconds(kLoginFadeoutTransitionDurationMs));
login_window_->SetVisibilityAnimationTransition(
views::Widget::ANIMATE_HIDE);
}
login_window_->AddRemovalsObserver(this);
login_window_->SetContentsView(login_view_);
// If WebUI is initialized in hidden state, show it only if we're no
// longer waiting for wallpaper animation/user images loading. Otherwise,
// always show it.
if (!initialize_webui_hidden_ || !waiting_for_wallpaper_load_) {
VLOG(1) << "Login WebUI >> show login wnd on create";
login_window_->Show();
} else {
VLOG(1) << "Login WebUI >> login wnd is hidden on create";
login_view_->set_is_hidden(true);
}
login_window_->GetNativeView()->SetName("WebUILoginView");
}
void LoginDisplayHostWebUI::ResetLoginWindowAndView() {
// Make sure to reset the |login_view_| pointer first; it is owned by
// |login_window_|. Closing |login_window_| could immediately invalidate the
// |login_view_| pointer.
if (login_view_) {
login_view_->SetUIEnabled(true);
login_view_ = nullptr;
}
if (login_window_) {
if (!features::IsAshInBrowserProcess()) {
login_window_->Close();
} else {
login_window_->Hide();
// This CompositorObserver becomes "owned" by login_window_ after
// construction and will delete itself once login_window_ is destroyed.
new CloseAfterCommit(login_window_);
}
login_window_->RemoveRemovalsObserver(this);
login_window_ = nullptr;
login_window_delegate_ = nullptr;
}
// Release wizard controller with the webui and hosting window so that it
// does not find missing webui handlers in surprise.
wizard_controller_.reset();
}
void LoginDisplayHostWebUI::SetOobeProgressBarVisible(bool visible) {
GetOobeUI()->ShowOobeUI(visible);
}
void LoginDisplayHostWebUI::TryToPlayOobeStartupSound() {
if (is_voice_interaction_oobe_)
return;
if (oobe_startup_sound_played_ || login_prompt_visible_time_.is_null() ||
!CrasAudioHandler::Get()->GetPrimaryActiveOutputNode()) {
return;
}
oobe_startup_sound_played_ = true;
// Don't try play startup sound if login prompt is already visible
// for a long time or can't be played.
if (base::TimeTicks::Now() - login_prompt_visible_time_ >
base::TimeDelta::FromMilliseconds(kStartupSoundMaxDelayMs)) {
return;
}
AccessibilityManager::Get()->PlayEarcon(SOUND_STARTUP,
PlaySoundOption::ALWAYS);
}
void LoginDisplayHostWebUI::OnLoginPromptVisible() {
if (!login_prompt_visible_time_.is_null())
return;
login_prompt_visible_time_ = base::TimeTicks::Now();
TryToPlayOobeStartupSound();
}
void LoginDisplayHostWebUI::CreateExistingUserController() {
// There can only be one |ExistingUserController| instance at a time.
existing_user_controller_.reset();
existing_user_controller_.reset(new ExistingUserController(this));
login_display_->set_delegate(existing_user_controller_.get());
}
// static
void LoginDisplayHostWebUI::DisableRestrictiveProxyCheckForTest() {
default_host()
->GetOobeUI()
->GetGaiaScreenView()
->DisableRestrictiveProxyCheckForTest();
}
void LoginDisplayHostWebUI::StartVoiceInteractionOobe() {
is_voice_interaction_oobe_ = true;
finalize_animation_type_ = ANIMATION_NONE;
StartWizard(OobeScreen::SCREEN_VOICE_INTERACTION_VALUE_PROP);
// We should emit this signal only at login screen (after reboot or sign out).
login_view_->set_should_emit_login_prompt_visible(false);
}
void LoginDisplayHostWebUI::ShowGaiaDialog(
bool can_close,
const base::Optional<AccountId>& prefilled_account) {
NOTREACHED();
}
void LoginDisplayHostWebUI::HideOobeDialog() {
NOTREACHED();
}
void LoginDisplayHostWebUI::UpdateOobeDialogSize(int width, int height) {
NOTREACHED();
}
const user_manager::UserList LoginDisplayHostWebUI::GetUsers() {
return user_manager::UserList();
}
void LoginDisplayHostWebUI::ShowFeedback() {
NOTREACHED();
}
void LoginDisplayHostWebUI::OnCancelPasswordChangedFlow() {}
////////////////////////////////////////////////////////////////////////////////
// external
// Declared in login_wizard.h so that others don't need to depend on our .h.
// TODO(nkostylev): Split this into a smaller functions.
void ShowLoginWizard(OobeScreen first_screen) {
if (browser_shutdown::IsTryingToQuit())
return;
VLOG(1) << "Showing OOBE screen: " << GetOobeScreenName(first_screen);
input_method::InputMethodManager* manager =
input_method::InputMethodManager::Get();
// Set up keyboards. For example, when |locale| is "en-US", enable US qwerty
// and US dvorak keyboard layouts.
if (g_browser_process && g_browser_process->local_state()) {
manager->GetActiveIMEState()->SetInputMethodLoginDefault();
PrefService* prefs = g_browser_process->local_state();
// Apply owner preferences for tap-to-click and mouse buttons swap for
// login screen.
system::InputDeviceSettings::Get()->SetPrimaryButtonRight(
prefs->GetBoolean(prefs::kOwnerPrimaryMouseButtonRight));
// TODO(jamescook): move to ash in OnLocalStatePrefServiceInitialized() once
// user session info could distinguish between owner and non-owner
// (http://crbug.com/857103).
system::InputDeviceSettings::Get()->SetTapToClick(
prefs->GetBoolean(ash::prefs::kOwnerTapToClickEnabled));
}
system::InputDeviceSettings::Get()->SetNaturalScroll(
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNaturalScrollDefault));
auto session_state = session_manager::SessionState::OOBE;
if (IsOobeComplete() || first_screen == OobeScreen::SCREEN_SPECIAL_LOGIN)
session_state = session_manager::SessionState::LOGIN_PRIMARY;
session_manager::SessionManager::Get()->SetSessionState(session_state);
bool show_app_launch_splash_screen =
(first_screen == OobeScreen::SCREEN_APP_LAUNCH_SPLASH);
if (show_app_launch_splash_screen) {
const std::string& auto_launch_app_id =
KioskAppManager::Get()->GetAutoLaunchApp();
const bool diagnostic_mode = false;
const bool auto_launch = true;
// Manages its own lifetime. See ShutdownDisplayHost().
auto* display_host = new LoginDisplayHostWebUI();
display_host->StartAppLaunch(auto_launch_app_id, diagnostic_mode,
auto_launch);
return;
}
// Check whether we need to execute OOBE flow.
const policy::EnrollmentConfig enrollment_config =
g_browser_process->platform_part()
->browser_policy_connector_chromeos()
->GetPrescribedEnrollmentConfig();
if (enrollment_config.should_enroll() &&
first_screen == OobeScreen::SCREEN_UNKNOWN) {
// Manages its own lifetime. See ShutdownDisplayHost().
auto* display_host = new LoginDisplayHostWebUI();
// Shows networks screen instead of enrollment screen to resume the
// interrupted auto start enrollment flow because enrollment screen does
// not handle flaky network. See http://crbug.com/332572
display_host->StartWizard(OobeScreen::SCREEN_OOBE_WELCOME);
return;
}
if (StartupUtils::IsEulaAccepted()) {
DelayNetworkCall(
base::TimeDelta::FromMilliseconds(kDefaultNetworkRetryDelayMS),
ServicesCustomizationDocument::GetInstance()
->EnsureCustomizationAppliedClosure());
g_browser_process->platform_part()
->GetTimezoneResolverManager()
->UpdateTimezoneResolver();
}
PrefService* prefs = g_browser_process->local_state();
std::string current_locale = prefs->GetString(prefs::kApplicationLocale);
language::ConvertToActualUILocale(&current_locale);
VLOG(1) << "Current locale: " << current_locale;
if (ShouldShowSigninScreen(first_screen)) {
std::string switch_locale = GetManagedLoginScreenLocale();
if (switch_locale == current_locale)
switch_locale.clear();
std::unique_ptr<ShowLoginWizardSwitchLanguageCallbackData> data =
std::make_unique<ShowLoginWizardSwitchLanguageCallbackData>(
first_screen, nullptr);
TriggerShowLoginWizardFinish(switch_locale, std::move(data));
return;
}
// Load startup manifest.
const StartupCustomizationDocument* startup_manifest =
StartupCustomizationDocument::GetInstance();
// Switch to initial locale if specified by customization
// and has not been set yet. We cannot call
// LanguageSwitchMenu::SwitchLanguage here before
// EmitLoginPromptReady.
const std::string& locale = startup_manifest->initial_locale_default();
const std::string& layout = startup_manifest->keyboard_layout();
VLOG(1) << "Initial locale: " << locale << "keyboard layout " << layout;
// Determine keyboard layout from OEM customization (if provided) or
// initial locale and save it in preferences.
manager->GetActiveIMEState()->SetInputMethodLoginDefaultFromVPD(locale,
layout);
std::unique_ptr<ShowLoginWizardSwitchLanguageCallbackData> data(
new ShowLoginWizardSwitchLanguageCallbackData(first_screen,
startup_manifest));
if (!current_locale.empty() || locale.empty()) {
TriggerShowLoginWizardFinish(std::string(), std::move(data));
return;
}
// Save initial locale from VPD/customization manifest as current
// Chrome locale. Otherwise it will be lost if Chrome restarts.
// Don't need to schedule pref save because setting initial local
// will enforce preference saving.
prefs->SetString(prefs::kApplicationLocale, locale);
StartupUtils::SetInitialLocale(locale);
TriggerShowLoginWizardFinish(locale, std::move(data));
}
} // namespace chromeos