blob: a3e4171bcdfe4f732c433663a21dd20a1ce54d7a [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/ash/login/ui/login_display_host_webui.h"
#include <memory>
#include <utility>
#include <vector>
#include "ash/accessibility/ui/focus_ring_controller.h"
#include "ash/components/audio/sounds.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/locale_update_controller.h"
#include "ash/public/cpp/login_screen.h"
#include "ash/public/cpp/login_screen_model.h"
#include "ash/public/cpp/multi_user_window_manager.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/histogram_functions.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/timer/elapsed_timer.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/app_mode/arc/arc_kiosk_app_manager.h"
#include "chrome/browser/ash/app_mode/kiosk_app_types.h"
#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
#include "chrome/browser/ash/base/locale_util.h"
#include "chrome/browser/ash/first_run/drive_first_run_controller.h"
#include "chrome/browser/ash/first_run/first_run.h"
#include "chrome/browser/ash/login/existing_user_controller.h"
#include "chrome/browser/ash/login/helper.h"
#include "chrome/browser/ash/login/login_wizard.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/login/ui/input_events_blocker.h"
#include "chrome/browser/ash/login/ui/login_display_host_mojo.h"
#include "chrome/browser/ash/login/ui/login_display_webui.h"
#include "chrome/browser/ash/login/ui/webui_accelerator_mapping.h"
#include "chrome/browser/ash/login/ui/webui_login_view.h"
#include "chrome/browser/ash/login/wizard_controller.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/enrollment/enrollment_config.h"
#include "chrome/browser/ash/policy/enrollment/enrollment_requisition_manager.h"
#include "chrome/browser/ash/settings/cros_settings.h"
#include "chrome/browser/ash/system/device_disabling_manager.h"
#include "chrome/browser/ash/system/input_device_settings.h"
#include "chrome/browser/ash/system/timezone_resolver_manager.h"
#include "chrome/browser/ash/system/timezone_util.h"
#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/boot_times_recorder.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/browser/chromeos/net/delay_network_call.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/ash_util.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
#include "chrome/browser/ui/ash/system_tray_client_impl.h"
#include "chrome/browser/ui/ash/wallpaper_controller_client_impl.h"
#include "chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/device_disabled_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/lacros_data_migration_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
#include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/welcome_screen_handler.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/dbus/session_manager/session_manager_client.h"
#include "chromeos/login/login_state/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/browser/pref_names.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 "services/audio/public/cpp/sounds/sounds_manager.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/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/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/events/devices/device_data_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/views/focus/focus_manager.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
namespace ash {
namespace {
// Maximum delay for startup sound after 'loginPromptVisible' signal.
const int kStartupSoundMaxDelayMs = 4000;
// 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 app launch splash WebUI.
const char kAppLaunchSplashURL[] = "chrome://oobe/app-launch-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;
struct DisplayScaleFactor {
int longest_side;
float scale_factor;
};
const DisplayScaleFactor k4KDisplay = {3840, 1.5f},
kMediumDisplay = {1440, 4.f / 3};
// A class to observe an implicit animation and invokes the callback after the
// animation is completed.
class AnimationObserver : public ui::ImplicitAnimationObserver {
public:
explicit AnimationObserver(base::OnceClosure callback)
: callback_(std::move(callback)) {}
~AnimationObserver() override {}
private:
// ui::ImplicitAnimationObserver implementation:
void OnImplicitAnimationsCompleted() override {
std::move(callback_).Run();
delete this;
}
base::OnceClosure 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::BrowserPolicyConnectorAsh* connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
// Oobe is completed and we have a user or we are enterprise enrolled.
return chromeos::StartupUtils::IsOobeCompleted() &&
(!user_manager::UserManager::Get()->GetUsers().empty() ||
connector->IsDeviceEnterpriseManaged());
}
// Returns true if signin (not oobe) should be displayed.
bool ShouldShowSigninScreen(chromeos::OobeScreenId first_screen) {
return (first_screen == chromeos::OobeScreen::SCREEN_UNKNOWN &&
IsOobeComplete());
}
void MaybeShowDeviceDisabledScreen() {
DCHECK(LoginDisplayHost::default_host());
if (!g_browser_process->platform_part()->device_disabling_manager()) {
// Device disabled check will be done in the DeviceDisablingManager.
return;
}
if (!system::DeviceDisablingManager::IsDeviceDisabledDuringNormalOperation())
return;
LoginDisplayHost::default_host()->StartWizard(
DeviceDisabledScreenView::kScreenId);
}
void MaybeShutdownLoginDisplayHostWebUI() {
if (!LoginDisplayHost::default_host())
return;
if (!LoginDisplayHost::default_host()->GetOobeUI())
return;
if (LoginDisplayHost::default_host()->GetOobeUI()->display_type() !=
OobeUI::kOobeDisplay) {
return;
}
LoginDisplayHost::default_host()->FinalizeImmediately();
CHECK(!LoginDisplayHost::default_host());
}
// 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::OobeScreenId first_screen,
const StartupCustomizationDocument* startup_manifest) {
TRACE_EVENT0("chromeos", "ShowLoginWizard::ShowLoginWizardFinish");
if (ShouldShowSigninScreen(first_screen)) {
// Shutdown WebUI host to replace with the Mojo one.
MaybeShutdownLoginDisplayHostWebUI();
}
// 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.
LoginDisplayHost* display_host = nullptr;
if (LoginDisplayHost::default_host()) {
// Tests may have already allocated an instance for us to use.
display_host = LoginDisplayHost::default_host();
} else if (ShouldShowSigninScreen(first_screen) ||
first_screen == LacrosDataMigrationScreenView::kScreenId) {
// TODO(crbug.com/1178702): Once lacros is officially released,
// `ShowLoginWizard()` will no longer be called with lacros screen id.
// Instead simply call `SigninUI::StartBrowserDataMigration()` as part of
// the login flow.
display_host = new LoginDisplayHostMojo(DisplayedScreen::SIGN_IN_SCREEN);
} else {
display_host = new LoginDisplayHostWebUI();
}
// Restore system timezone.
std::string timezone;
if (system::PerUserTimezoneEnabled()) {
timezone = g_browser_process->local_state()->GetString(
prefs::kSigninScreenTimezone);
}
// TODO(crbug.com/1105387): Part of initial screen logic.
if (ShouldShowSigninScreen(first_screen)) {
display_host->StartSignInScreen();
} 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())
system::SetSystemAndSigninScreenTimezone(timezone);
// This step requires the session manager to have been initialized and login
// display host to be created.
DCHECK(session_manager::SessionManager::Get());
DCHECK(LoginDisplayHost::default_host());
WallpaperControllerClientImpl::Get()->SetInitialWallpaper();
// TODO(crbug.com/1105387): Part of initial screen logic.
MaybeShowDeviceDisabledScreen();
}
struct ShowLoginWizardSwitchLanguageCallbackData {
explicit ShowLoginWizardSwitchLanguageCallbackData(
chromeos::OobeScreenId first_screen,
const StartupCustomizationDocument* startup_manifest)
: first_screen(first_screen), startup_manifest(startup_manifest) {}
const chromeos::OobeScreenId first_screen;
const StartupCustomizationDocument* const startup_manifest;
// lock UI while resource bundle is being reloaded.
InputEventsBlocker events_blocker;
};
// Trigger OnLocaleChanged via LocaleUpdateController.
void NotifyLocaleChange() {
LocaleUpdateController::Get()->OnLocaleChanged();
}
void OnLanguageSwitchedCallback(
std::unique_ptr<ShowLoginWizardSwitchLanguageCallbackData> self,
const locale_util::LanguageSwitchResult& result) {
if (!result.success)
LOG(WARNING) << "Locale could not be found for '" << result.requested_locale
<< "'";
// Notify the locale change.
NotifyLocaleChange();
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 {
locale_util::SwitchLanguageCallback callback(
base::BindOnce(&OnLanguageSwitchedCallback, std::move(data)));
// Load locale keyboards here. Hardware layout would be automatically
// enabled.
locale_util::SwitchLanguage(
switch_locale, true, true /* login_layouts_only */, std::move(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() {
auto* cros_settings = 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->GetList().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() {
auto* client = ChromeKeyboardControllerClient::Get();
keyboard::KeyboardConfig config = client->GetKeyboardConfig();
config.overscroll_behavior = keyboard::KeyboardOverscrollBehavior::kDisabled;
client->SetKeyboardConfig(config);
}
void ResetKeyboardOverscrollBehavior() {
auto* client = ChromeKeyboardControllerClient::Get();
keyboard::KeyboardConfig config = client->GetKeyboardConfig();
config.overscroll_behavior = keyboard::KeyboardOverscrollBehavior::kDefault;
client->SetKeyboardConfig(config);
}
// Workaround for graphical glitches with animated user avatars due to a race
// between GPU process cleanup for the closing WebContents versus compositor
// draw of new animation frames. https://crbug.com/759148
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);
CHECK(!IsInObserverList());
}
// ui::CompositorObserver:
void OnCompositingDidCommit(ui::Compositor* compositor) override {
DCHECK_EQ(widget_->GetCompositor(), compositor);
widget_->Close();
}
// views::WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override {
DCHECK_EQ(widget, widget_);
delete this;
}
private:
views::Widget* const widget_;
DISALLOW_COPY_AND_ASSIGN(CloseAfterCommit);
};
// Returns true if we have default audio device.
bool CanPlayStartupSound() {
AudioDevice device;
bool found = CrasAudioHandler::Get()->GetPrimaryActiveOutputDevice(&device);
return found && device.stable_device_id_version &&
device.type != AudioDeviceType::kOther;
}
} // namespace
// static
const char LoginDisplayHostWebUI::kShowLoginWebUIid[] = "ShowLoginWebUI";
bool LoginDisplayHostWebUI::disable_restrictive_proxy_check_for_test_ = false;
// A class to handle special menu key for keyboard driven OOBE.
class LoginDisplayHostWebUI::KeyboardDrivenOobeKeyHandler
: public ui::EventHandler {
public:
KeyboardDrivenOobeKeyHandler() { Shell::Get()->AddPreTargetHandler(this); }
~KeyboardDrivenOobeKeyHandler() override {
Shell::Get()->RemovePreTargetHandler(this);
}
private:
// ui::EventHandler
void OnKeyEvent(ui::KeyEvent* event) override {
if (event->key_code() == ui::VKEY_F6) {
SystemTrayClientImpl::Get()->SetPrimaryTrayVisible(false);
event->StopPropagation();
}
}
DISALLOW_COPY_AND_ASSIGN(KeyboardDrivenOobeKeyHandler);
};
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, public
LoginDisplayHostWebUI::LoginDisplayHostWebUI()
: oobe_startup_sound_played_(StartupUtils::IsOobeCompleted()) {
SessionManagerClient::Get()->AddObserver(this);
CrasAudioHandler::Get()->AddAudioObserver(this);
ui::DeviceDataManager::GetInstance()->AddObserver(this);
// When we wait for WebUI to be initialized we wait for one of
// these notifications.
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());
audio::SoundsManager* manager = audio::SoundsManager::Get();
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
manager->Initialize(static_cast<int>(Sound::kStartup),
bundle.GetRawDataResource(IDR_SOUND_STARTUP_WAV));
login_display_ = std::make_unique<LoginDisplayWebUI>();
}
LoginDisplayHostWebUI::~LoginDisplayHostWebUI() {
VLOG(4) << "~LoginDisplayWebUI";
if (GetOobeUI())
GetOobeUI()->signin_screen_handler()->SetDelegate(nullptr);
SessionManagerClient::Get()->RemoveObserver(this);
CrasAudioHandler::Get()->RemoveAudioObserver(this);
if (waiting_for_configuration_) {
OobeConfiguration::Get()->RemoveObserver(this);
waiting_for_configuration_ = false;
}
ui::DeviceDataManager::GetInstance()->RemoveObserver(this);
if (login_view_ && login_window_)
login_window_->RemoveRemovalsObserver(this);
auto* window_manager = MultiUserWindowManagerHelper::GetWindowManager();
// MultiUserWindowManagerHelper instance might be null if no user is logged
// in - or in a unit test.
if (window_manager)
window_manager->RemoveObserver(this);
ResetKeyboardOverscrollBehavior();
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();
}
CHECK(!views::WidgetObserver::IsInObserverList());
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, LoginDisplayHost:
LoginDisplay* LoginDisplayHostWebUI::GetLoginDisplay() {
return login_display_.get();
}
ExistingUserController* LoginDisplayHostWebUI::GetExistingUserController() {
return existing_user_controller_.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:
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;
}
}
void LoginDisplayHostWebUI::SetStatusAreaVisible(bool visible) {
status_area_saved_visibility_ = visible;
if (login_view_)
login_view_->SetStatusAreaVisible(status_area_saved_visibility_);
}
void LoginDisplayHostWebUI::OnOobeConfigurationChanged() {
waiting_for_configuration_ = false;
OobeConfiguration::Get()->RemoveObserver(this);
StartWizard(first_screen_);
}
void LoginDisplayHostWebUI::StartWizard(OobeScreenId first_screen) {
if (!StartupUtils::IsOobeCompleted()) {
CHECK(OobeConfiguration::Get());
if (waiting_for_configuration_)
return;
if (!OobeConfiguration::Get()->CheckCompleted()) {
waiting_for_configuration_ = true;
first_screen_ = first_screen;
OobeConfiguration::Get()->AddAndFireObserver(this);
VLOG(1) << "Login WebUI >> wizard waiting for configuration check";
return;
}
}
DisableKeyboardOverscroll();
TryToPlayOobeStartupSound();
// Keep parameters to restore if renderer crashes.
restore_path_ = RESTORE_WIZARD;
first_screen_ = first_screen;
is_showing_login_ = WizardController::IsSigninScreen(first_screen);
VLOG(1) << "Login WebUI >> wizard";
if (!login_window_) {
oobe_load_timer_ = base::ElapsedTimer();
LoadURL(GURL(kOobeURL));
}
DVLOG(1) << "Starting wizard, first_screen: " << first_screen;
oobe_progress_bar_visible_ = !StartupUtils::IsDeviceRegistered();
SetOobeProgressBarVisible(oobe_progress_bar_visible_);
// Create and show the wizard.
if (wizard_controller_) {
wizard_controller_->AdvanceToScreen(first_screen);
} else {
wizard_controller_ = std::make_unique<WizardController>();
wizard_controller_->Init(first_screen);
}
}
WizardController* LoginDisplayHostWebUI::GetWizardController() {
return wizard_controller_.get();
}
void LoginDisplayHostWebUI::OnStartUserAdding() {
NOTREACHED();
}
void LoginDisplayHostWebUI::CancelUserAdding() {
NOTREACHED();
}
void LoginDisplayHostWebUI::OnStartSignInScreen() {
DisableKeyboardOverscroll();
restore_path_ = RESTORE_SIGN_IN;
is_showing_login_ = true;
finalize_animation_type_ = ANIMATION_WORKSPACE;
VLOG(1) << "Login WebUI >> sign in";
// TODO(crbug.com/784495): Make sure this is ported to views.
if (!login_window_) {
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
"ui", "ShowLoginWebUI",
TRACE_ID_WITH_SCOPE(kShowLoginWebUIid, TRACE_ID_GLOBAL(1)));
TRACE_EVENT_NESTABLE_ASYNC_INSTANT0(
"ui", "StartSignInScreen",
TRACE_ID_WITH_SCOPE(kShowLoginWebUIid, TRACE_ID_GLOBAL(1)));
BootTimesRecorder::Get()->RecordCurrentStats("login-start-signin-screen");
LoadURL(GURL(kLoginURL));
}
DVLOG(1) << "Starting sign in screen";
CreateExistingUserController();
// 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(login_display_.get());
OnStartSignInScreenCommon();
TRACE_EVENT_NESTABLE_ASYNC_INSTANT0(
"ui", "WaitForScreenStateInitialize",
TRACE_ID_WITH_SCOPE(kShowLoginWebUIid, TRACE_ID_GLOBAL(1)));
// 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() {
finalize_animation_type_ = ANIMATION_FADE_OUT;
if (!login_window_)
LoadURL(GURL(kAppLaunchSplashURL));
login_view_->set_should_emit_login_prompt_visible(false);
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, public
void LoginDisplayHostWebUI::OnBrowserCreated() {
VLOG(4) << "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";
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());
}
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, WebContentsObserver:
void LoginDisplayHostWebUI::RenderProcessGone(base::TerminationStatus status) {
// Do not try to restore on shutdown
if (browser_shutdown::HasShutdownStarted())
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, CrasAudioHandler::AudioObserver:
void LoginDisplayHostWebUI::OnActiveOutputNodeChanged() {
PlayStartupSoundIfPossible();
}
////////////////////////////////////////////////////////////////////////////////
// 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 (switches::ShouldScaleOobe() &&
policy::EnrollmentRequisitionManager::IsRemoraRequisition()) {
UpScaleOobe();
}
if (GetOobeUI()) {
GetOobeUI()->GetCoreOobeView()->UpdateClientAreaSize(
primary_display.size());
if (changed_metrics & DISPLAY_METRIC_PRIMARY)
GetOobeUI()->OnDisplayConfigurationChanged();
}
}
void LoginDisplayHostWebUI::UpScaleOobe() {
const int64_t display_id =
display::Screen::GetScreen()->GetPrimaryDisplay().id();
if (primary_display_id_ == display_id) {
return;
}
primary_display_id_ = display_id;
auto* display_manager = Shell::Get()->display_manager();
const gfx::Size size =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area_size();
const int longest_side = std::max(size.width(), size.height());
if (longest_side >= k4KDisplay.longest_side) {
display_manager->UpdateZoomFactor(display_id, k4KDisplay.scale_factor);
} else if (longest_side >= kMediumDisplay.longest_side) {
display_manager->UpdateZoomFactor(display_id, kMediumDisplay.scale_factor);
}
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, ui::InputDeviceEventObserver
void LoginDisplayHostWebUI::OnInputDeviceConfigurationChanged(
uint8_t input_device_types) {
if ((input_device_types & ui::InputDeviceEventObserver::kTouchscreen) &&
GetOobeUI()) {
GetOobeUI()->OnDisplayConfigurationChanged();
}
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, views::WidgetRemovalsObserver:
void LoginDisplayHostWebUI::OnWillRemoveView(views::Widget* widget,
views::View* view) {
if (view != static_cast<views::View*>(login_view_))
return;
ResetLoginView();
widget->RemoveRemovalsObserver(this);
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, views::WidgetObserver:
void LoginDisplayHostWebUI::OnWidgetDestroying(views::Widget* widget) {
DCHECK_EQ(login_window_, widget);
login_window_->RemoveRemovalsObserver(this);
login_window_->RemoveObserver(this);
login_window_ = nullptr;
ResetLoginView();
}
void LoginDisplayHostWebUI::OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& new_bounds) {
for (auto& observer : observers_)
observer.WebDialogViewBoundsChanged(new_bounds);
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, MultiUserWindowManagerObserver:
void LoginDisplayHostWebUI::OnUserSwitchAnimationFinished() {
ShutdownDisplayHost();
}
////////////////////////////////////////////////////////////////////////////////
// LoginDisplayHostWebUI, private
void LoginDisplayHostWebUI::ScheduleWorkspaceAnimation() {
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableLoginAnimations)) {
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::BindOnce(&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() {
DCHECK(login_window_);
DCHECK(login_view_);
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();
if (oobe_load_timer_.has_value()) {
base::UmaHistogramTimes("OOBE.WebUI.LoadTime.FirstRun",
oobe_load_timer_->Elapsed());
oobe_load_timer_.reset();
}
}
void LoginDisplayHostWebUI::InitLoginWindowAndView() {
if (login_window_)
return;
if (system::InputDeviceSettings::ForceKeyboardDrivenUINavigation()) {
views::FocusManager::set_arrow_key_traversal_enabled(true);
focus_ring_controller_ = std::make_unique<FocusRingController>();
focus_ring_controller_->SetVisible(true);
keyboard_driven_oobe_key_handler_ =
std::make_unique<KeyboardDrivenOobeKeyHandler>();
}
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = CalculateScreenBounds(gfx::Size());
params.show_state = ui::SHOW_STATE_FULLSCREEN;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
ash_util::SetupWidgetInitParamsForContainer(
&params, kShellWindowId_LockScreenContainer);
login_window_ = new views::Widget;
login_window_->Init(std::move(params));
login_view_ = new WebUILoginView(WebUILoginView::WebViewSettings(),
weak_factory_.GetWeakPtr());
login_view_->Init();
if (login_view_->webui_visible())
OnLoginPromptVisible();
if (disable_restrictive_proxy_check_for_test_) {
DisableRestrictiveProxyCheckForTest();
}
login_window_->SetVisibilityAnimationDuration(
base::TimeDelta::FromMilliseconds(kLoginFadeoutTransitionDurationMs));
login_window_->SetVisibilityAnimationTransition(views::Widget::ANIMATE_HIDE);
login_window_->AddObserver(this);
login_window_->AddRemovalsObserver(this);
login_window_->SetContentsView(login_view_);
login_window_->GetNativeView()->SetName("WebUILoginView");
// Delay showing the window until the login webui is ready.
VLOG(1) << "Login WebUI >> login window is hidden on create";
login_view_->set_is_hidden(true);
}
void LoginDisplayHostWebUI::ResetLoginWindowAndView() {
VLOG(4) << "ResetLoginWindowAndView";
// Notify any oobe dialog state observers (e.g. login shelf) that the UI is
// hidden (so they can reset any cached OOBE dialog state.)
LoginScreen::Get()->GetModel()->NotifyOobeDialogState(
OobeDialogState::HIDDEN);
// 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);
ResetLoginView();
}
if (login_window_) {
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_->RemoveObserver(this);
login_window_ = 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() {
need_to_play_startup_sound_ = true;
PlayStartupSoundIfPossible();
}
void LoginDisplayHostWebUI::ResetLoginView() {
if (!login_view_)
return;
OobeUI* oobe_ui = login_view_->GetOobeUI();
if (oobe_ui)
oobe_ui->signin_screen_handler()->SetDelegate(nullptr);
login_view_ = nullptr;
}
void LoginDisplayHostWebUI::OnLoginPromptVisible() {
if (!login_prompt_visible_time_.is_null())
return;
login_prompt_visible_time_ = base::TimeTicks::Now();
TryToPlayOobeStartupSound();
}
void LoginDisplayHostWebUI::CreateExistingUserController() {
existing_user_controller_ = std::make_unique<ExistingUserController>();
login_display_->set_delegate(existing_user_controller_.get());
}
// static
void LoginDisplayHostWebUI::DisableRestrictiveProxyCheckForTest() {
if (default_host() && default_host()->GetOobeUI()) {
default_host()
->GetOobeUI()
->GetView<GaiaScreenHandler>()
->DisableRestrictiveProxyCheckForTest();
disable_restrictive_proxy_check_for_test_ = false;
} else {
disable_restrictive_proxy_check_for_test_ = true;
}
}
void LoginDisplayHostWebUI::ShowGaiaDialog(const AccountId& prefilled_account) {
ShowGaiaDialogCommon(prefilled_account);
}
void LoginDisplayHostWebUI::HideOobeDialog() {
NOTREACHED();
}
void LoginDisplayHostWebUI::SetShelfButtonsEnabled(bool enabled) {
LoginScreen::Get()->EnableShelfButtons(enabled);
if (GetWebUILoginView())
GetWebUILoginView()->set_shelf_enabled(enabled);
}
void LoginDisplayHostWebUI::UpdateOobeDialogState(OobeDialogState state) {
LoginScreen::Get()->GetModel()->NotifyOobeDialogState(state);
}
void LoginDisplayHostWebUI::HandleDisplayCaptivePortal() {
GetOobeUI()->GetErrorScreen()->FixCaptivePortal();
}
void LoginDisplayHostWebUI::OnCancelPasswordChangedFlow() {}
void LoginDisplayHostWebUI::ShowEnableConsumerKioskScreen() {
if (GetExistingUserController())
GetExistingUserController()->OnStartKioskEnableScreen();
}
void LoginDisplayHostWebUI::UpdateAddUserButtonStatus() {
NOTREACHED();
}
void LoginDisplayHostWebUI::RequestSystemInfoUpdate() {
NOTREACHED();
}
bool LoginDisplayHostWebUI::HasUserPods() {
return false;
}
void LoginDisplayHostWebUI::VerifyOwnerForKiosk(base::OnceClosure) {
NOTREACHED();
}
void LoginDisplayHostWebUI::ShowPasswordChangedDialog(
const AccountId& account_id,
bool show_password_error) {
NOTREACHED();
}
void LoginDisplayHostWebUI::StartBrowserDataMigration() {
NOTREACHED();
}
void LoginDisplayHostWebUI::AddObserver(LoginDisplayHost::Observer* observer) {
observers_.AddObserver(observer);
}
void LoginDisplayHostWebUI::RemoveObserver(
LoginDisplayHost::Observer* observer) {
observers_.RemoveObserver(observer);
}
void LoginDisplayHostWebUI::PlayStartupSoundIfPossible() {
if (!need_to_play_startup_sound_ || oobe_startup_sound_played_)
return;
if (login_prompt_visible_time_.is_null())
return;
if (!CanPlayStartupSound())
return;
need_to_play_startup_sound_ = false;
oobe_startup_sound_played_ = true;
const base::TimeDelta time_since_login_prompt_visible =
base::TimeTicks::Now() - login_prompt_visible_time_;
base::UmaHistogramTimes("Accessibility.OOBEStartupSoundDelay",
time_since_login_prompt_visible);
// Don't try to play startup sound if login prompt has been already visible
// for a long time.
if (time_since_login_prompt_visible >
base::TimeDelta::FromMilliseconds(kStartupSoundMaxDelayMs)) {
return;
}
AccessibilityManager::Get()->PlayEarcon(Sound::kStartup,
PlaySoundOption::kAlways);
}
////////////////////////////////////////////////////////////////////////////////
// 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(OobeScreenId first_screen) {
if (browser_shutdown::IsTryingToQuit())
return;
VLOG(1) << "Showing OOBE screen: " << 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));
system::InputDeviceSettings::Get()->SetPointingStickPrimaryButtonRight(
prefs->GetBoolean(prefs::kOwnerPrimaryPointingStickButtonRight));
system::InputDeviceSettings::Get()->SetTapToClick(
prefs->GetBoolean(prefs::kOwnerTapToClickEnabled));
}
system::InputDeviceSettings::Get()->SetNaturalScroll(
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNaturalScrollDefault));
auto session_state = session_manager::SessionState::OOBE;
if (IsOobeComplete())
session_state = session_manager::SessionState::LOGIN_PRIMARY;
session_manager::SessionManager::Get()->SetSessionState(session_state);
bool show_app_launch_splash_screen =
(first_screen == AppLaunchSplashScreenView::kScreenId);
if (show_app_launch_splash_screen) {
// Manages its own lifetime. See ShutdownDisplayHost().
auto* display_host = new LoginDisplayHostWebUI();
KioskAppId kiosk_app_id;
const std::string& chrome_kiosk_app_id =
KioskAppManager::Get()->GetAutoLaunchApp();
const AccountId& web_kiosk_account_id =
WebKioskAppManager::Get()->GetAutoLaunchAccountId();
const AccountId& arc_kiosk_account_id =
ArcKioskAppManager::Get()->GetAutoLaunchAccountId();
if (!chrome_kiosk_app_id.empty())
kiosk_app_id = KioskAppId::ForChromeApp(chrome_kiosk_app_id);
else if (web_kiosk_account_id.is_valid())
kiosk_app_id = KioskAppId::ForWebApp(web_kiosk_account_id);
else if (arc_kiosk_account_id.is_valid())
kiosk_app_id = KioskAppId::ForArcApp(arc_kiosk_account_id);
display_host->StartKiosk(kiosk_app_id, /* auto_launch */ true);
return;
}
// Check whether we need to execute OOBE flow.
const policy::EnrollmentConfig enrollment_config =
g_browser_process->platform_part()
->browser_policy_connector_ash()
->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(WelcomeView::kScreenId);
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(language::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(language::prefs::kApplicationLocale, locale);
StartupUtils::SetInitialLocale(locale);
TriggerShowLoginWizardFinish(locale, std::move(data));
}
class WebUIToViewsSwitchMetricsReporter : public content::NotificationObserver {
public:
WebUIToViewsSwitchMetricsReporter() {
registrar_.Add(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
content::NotificationService::AllSources());
}
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
DCHECK_EQ(LoginDisplayHost::default_host()->GetOobeUI()->display_type(),
OobeUI::kGaiaSigninDisplay);
base::UmaHistogramTimes("OOBE.WebUIToViewsSwitch.Duration",
timer_.Elapsed());
registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
content::NotificationService::AllSources());
base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
private:
content::NotificationRegistrar registrar_;
base::ElapsedTimer timer_;
};
void SwitchWebUItoMojo() {
DCHECK_EQ(LoginDisplayHost::default_host()->GetOobeUI()->display_type(),
OobeUI::kOobeDisplay);
// The object deletes itself.
new WebUIToViewsSwitchMetricsReporter();
// This replaces WebUI host with the Mojo (views) host.
ShowLoginWizard(OobeScreen::SCREEN_UNKNOWN);
}
} // namespace ash