1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/login/webui_login_view.h" 6 7 #include "ash/shell.h" 8 #include "ash/system/tray/system_tray.h" 9 #include "base/bind.h" 10 #include "base/callback.h" 11 #include "base/debug/trace_event.h" 12 #include "base/i18n/rtl.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/values.h" 15 #include "chrome/browser/chrome_notification_types.h" 16 #include "chrome/browser/chromeos/accessibility/accessibility_util.h" 17 #include "chrome/browser/chromeos/login/login_display_host_impl.h" 18 #include "chrome/browser/chromeos/login/proxy_settings_dialog.h" 19 #include "chrome/browser/chromeos/login/webui_login_display.h" 20 #include "chrome/browser/chromeos/profiles/profile_helper.h" 21 #include "chrome/browser/extensions/extension_web_contents_observer.h" 22 #include "chrome/browser/media/media_stream_infobar_delegate.h" 23 #include "chrome/browser/password_manager/password_manager.h" 24 #include "chrome/browser/password_manager/password_manager_delegate_impl.h" 25 #include "chrome/browser/renderer_preferences_util.h" 26 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h" 27 #include "chrome/common/render_messages.h" 28 #include "chromeos/dbus/dbus_thread_manager.h" 29 #include "chromeos/dbus/session_manager_client.h" 30 #include "chromeos/network/network_state.h" 31 #include "chromeos/network/network_state_handler.h" 32 #include "components/web_modal/web_contents_modal_dialog_manager.h" 33 #include "content/public/browser/notification_service.h" 34 #include "content/public/browser/render_view_host.h" 35 #include "content/public/browser/render_widget_host_view.h" 36 #include "content/public/browser/web_contents.h" 37 #include "content/public/browser/web_contents_view.h" 38 #include "content/public/browser/web_ui.h" 39 #include "ui/gfx/rect.h" 40 #include "ui/gfx/size.h" 41 #include "ui/views/controls/webview/webview.h" 42 #include "ui/views/widget/widget.h" 43 44 using content::NativeWebKeyboardEvent; 45 using content::RenderViewHost; 46 using content::WebContents; 47 using web_modal::WebContentsModalDialogManager; 48 49 namespace { 50 51 // These strings must be kept in sync with handleAccelerator() 52 // in display_manager.js. 53 const char kAccelNameCancel[] = "cancel"; 54 const char kAccelNameEnrollment[] = "enrollment"; 55 const char kAccelNameKioskEnable[] = "kiosk_enable"; 56 const char kAccelNameVersion[] = "version"; 57 const char kAccelNameReset[] = "reset"; 58 const char kAccelFocusPrev[] = "focus_prev"; 59 const char kAccelFocusNext[] = "focus_next"; 60 const char kAccelNameDeviceRequisition[] = "device_requisition"; 61 const char kAccelNameDeviceRequisitionRemora[] = "device_requisition_remora"; 62 const char kAccelNameAppLaunchBailout[] = "app_launch_bailout"; 63 64 // A class to change arrow key traversal behavior when it's alive. 65 class ScopedArrowKeyTraversal { 66 public: 67 explicit ScopedArrowKeyTraversal(bool new_arrow_key_tranversal_enabled) 68 : previous_arrow_key_traversal_enabled_( 69 views::FocusManager::arrow_key_traversal_enabled()) { 70 views::FocusManager::set_arrow_key_traversal_enabled( 71 new_arrow_key_tranversal_enabled); 72 } 73 ~ScopedArrowKeyTraversal() { 74 views::FocusManager::set_arrow_key_traversal_enabled( 75 previous_arrow_key_traversal_enabled_); 76 } 77 78 private: 79 const bool previous_arrow_key_traversal_enabled_; 80 DISALLOW_COPY_AND_ASSIGN(ScopedArrowKeyTraversal); 81 }; 82 83 } // namespace 84 85 namespace chromeos { 86 87 // static 88 const char WebUILoginView::kViewClassName[] = 89 "browser/chromeos/login/WebUILoginView"; 90 91 // WebUILoginView public: ------------------------------------------------------ 92 93 WebUILoginView::WebUILoginView() 94 : webui_login_(NULL), 95 is_hidden_(false), 96 webui_visible_(false), 97 should_emit_login_prompt_visible_(true), 98 forward_keyboard_event_(true) { 99 registrar_.Add(this, 100 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, 101 content::NotificationService::AllSources()); 102 registrar_.Add(this, 103 chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN, 104 content::NotificationService::AllSources()); 105 106 accel_map_[ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)] = 107 kAccelNameCancel; 108 accel_map_[ui::Accelerator(ui::VKEY_E, 109 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] = 110 kAccelNameEnrollment; 111 accel_map_[ui::Accelerator(ui::VKEY_K, 112 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] = 113 kAccelNameKioskEnable; 114 accel_map_[ui::Accelerator(ui::VKEY_V, ui::EF_ALT_DOWN)] = 115 kAccelNameVersion; 116 accel_map_[ui::Accelerator(ui::VKEY_R, 117 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)] = 118 kAccelNameReset; 119 120 accel_map_[ui::Accelerator(ui::VKEY_LEFT, ui::EF_NONE)] = 121 kAccelFocusPrev; 122 accel_map_[ui::Accelerator(ui::VKEY_RIGHT, ui::EF_NONE)] = 123 kAccelFocusNext; 124 125 // Use KEY_RELEASED because Gaia consumes KEY_PRESSED for up/down key. 126 ui::Accelerator key_up(ui::VKEY_UP, ui::EF_NONE); 127 key_up.set_type(ui::ET_KEY_RELEASED); 128 ui::Accelerator key_down(ui::VKEY_DOWN, ui::EF_NONE); 129 key_down.set_type(ui::ET_KEY_RELEASED); 130 accel_map_[key_up] = kAccelFocusPrev; 131 accel_map_[key_down] = kAccelFocusNext; 132 133 accel_map_[ui::Accelerator( 134 ui::VKEY_D, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)] = 135 kAccelNameDeviceRequisition; 136 accel_map_[ 137 ui::Accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] = 138 kAccelNameDeviceRequisitionRemora; 139 140 accel_map_[ui::Accelerator(ui::VKEY_S, 141 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] = 142 kAccelNameAppLaunchBailout; 143 144 for (AccelMap::iterator i(accel_map_.begin()); i != accel_map_.end(); ++i) 145 AddAccelerator(i->first); 146 } 147 148 WebUILoginView::~WebUILoginView() { 149 FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, 150 observer_list_, 151 OnHostDestroying()); 152 153 if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) { 154 ash::Shell::GetInstance()->GetPrimarySystemTray()-> 155 SetNextFocusableView(NULL); 156 } 157 } 158 159 void WebUILoginView::Init() { 160 Profile* signin_profile = ProfileHelper::GetSigninProfile(); 161 auth_extension_.reset(new ScopedGaiaAuthExtension(signin_profile)); 162 webui_login_ = new views::WebView(signin_profile); 163 webui_login_->set_allow_accelerators(true); 164 AddChildView(webui_login_); 165 166 WebContents* web_contents = webui_login_->GetWebContents(); 167 168 // Create the password manager that is needed for the proxy. 169 PasswordManagerDelegateImpl::CreateForWebContents(web_contents); 170 PasswordManager::CreateForWebContentsAndDelegate( 171 web_contents, PasswordManagerDelegateImpl::FromWebContents(web_contents)); 172 173 // LoginHandlerViews uses a constrained window for the password manager view. 174 WebContentsModalDialogManager::CreateForWebContents(web_contents); 175 WebContentsModalDialogManager::FromWebContents(web_contents)-> 176 SetDelegate(this); 177 178 web_contents->SetDelegate(this); 179 extensions::ExtensionWebContentsObserver::CreateForWebContents(web_contents); 180 WebContentsObserver::Observe(web_contents); 181 renderer_preferences_util::UpdateFromSystemSettings( 182 web_contents->GetMutableRendererPrefs(), 183 signin_profile); 184 } 185 186 const char* WebUILoginView::GetClassName() const { 187 return kViewClassName; 188 } 189 190 web_modal::WebContentsModalDialogHost* 191 WebUILoginView::GetWebContentsModalDialogHost() { 192 return this; 193 } 194 195 gfx::NativeView WebUILoginView::GetHostView() const { 196 return GetWidget()->GetNativeView(); 197 } 198 199 gfx::Point WebUILoginView::GetDialogPosition(const gfx::Size& size) { 200 // Center the widget. 201 gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size(); 202 return gfx::Point(widget_size.width() / 2 - size.width() / 2, 203 widget_size.height() / 2 - size.height() / 2); 204 } 205 206 gfx::Size WebUILoginView::GetMaximumDialogSize() { 207 return GetWidget()->GetWindowBoundsInScreen().size(); 208 } 209 210 void WebUILoginView::AddObserver( 211 web_modal::ModalDialogHostObserver* observer) { 212 if (observer && !observer_list_.HasObserver(observer)) 213 observer_list_.AddObserver(observer); 214 } 215 216 void WebUILoginView::RemoveObserver( 217 web_modal::ModalDialogHostObserver* observer) { 218 observer_list_.RemoveObserver(observer); 219 } 220 221 bool WebUILoginView::AcceleratorPressed( 222 const ui::Accelerator& accelerator) { 223 AccelMap::const_iterator entry = accel_map_.find(accelerator); 224 if (entry == accel_map_.end()) 225 return false; 226 227 if (!webui_login_) 228 return true; 229 230 content::WebUI* web_ui = GetWebUI(); 231 if (web_ui) { 232 base::StringValue accel_name(entry->second); 233 web_ui->CallJavascriptFunction("cr.ui.Oobe.handleAccelerator", 234 accel_name); 235 } 236 237 return true; 238 } 239 240 gfx::NativeWindow WebUILoginView::GetNativeWindow() const { 241 return GetWidget()->GetNativeWindow(); 242 } 243 244 void WebUILoginView::LoadURL(const GURL & url) { 245 webui_login_->LoadInitialURL(url); 246 webui_login_->RequestFocus(); 247 248 // TODO(nkostylev): Use WebContentsObserver::RenderViewCreated to track 249 // when RenderView is created. 250 // Use a background with transparency to trigger transparency in Webkit. 251 SkBitmap background; 252 background.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); 253 background.allocPixels(); 254 background.eraseARGB(0x00, 0x00, 0x00, 0x00); 255 content::RenderViewHost* host = GetWebContents()->GetRenderViewHost(); 256 host->GetView()->SetBackground(background); 257 } 258 259 content::WebUI* WebUILoginView::GetWebUI() { 260 return webui_login_->web_contents()->GetWebUI(); 261 } 262 263 content::WebContents* WebUILoginView::GetWebContents() { 264 return webui_login_->web_contents(); 265 } 266 267 void WebUILoginView::OpenProxySettings() { 268 const NetworkState* network = 269 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 270 if (!network) { 271 LOG(ERROR) << "No default network found!"; 272 return; 273 } 274 ProxySettingsDialog* dialog = 275 new ProxySettingsDialog(*network, NULL, GetNativeWindow()); 276 dialog->Show(); 277 } 278 279 void WebUILoginView::OnPostponedShow() { 280 set_is_hidden(false); 281 OnLoginPromptVisible(); 282 } 283 284 void WebUILoginView::SetStatusAreaVisible(bool visible) { 285 if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) { 286 ash::SystemTray* tray = ash::Shell::GetInstance()->GetPrimarySystemTray(); 287 if (visible) { 288 // Tray may have been initialized being hidden. 289 tray->SetVisible(visible); 290 tray->GetWidget()->Show(); 291 } else { 292 tray->GetWidget()->Hide(); 293 } 294 } 295 } 296 297 void WebUILoginView::SetUIEnabled(bool enabled) { 298 forward_keyboard_event_ = enabled; 299 ash::Shell::GetInstance()->GetPrimarySystemTray()->SetEnabled(enabled); 300 } 301 302 // WebUILoginView protected: --------------------------------------------------- 303 304 void WebUILoginView::Layout() { 305 DCHECK(webui_login_); 306 webui_login_->SetBoundsRect(bounds()); 307 308 FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, 309 observer_list_, 310 OnPositionRequiresUpdate()); 311 } 312 313 void WebUILoginView::OnLocaleChanged() { 314 } 315 316 void WebUILoginView::ChildPreferredSizeChanged(View* child) { 317 Layout(); 318 SchedulePaint(); 319 } 320 321 void WebUILoginView::AboutToRequestFocusFromTabTraversal(bool reverse) { 322 // Return the focus to the web contents. 323 webui_login_->web_contents()->FocusThroughTabTraversal(reverse); 324 GetWidget()->Activate(); 325 webui_login_->web_contents()->GetView()->Focus(); 326 } 327 328 void WebUILoginView::Observe(int type, 329 const content::NotificationSource& source, 330 const content::NotificationDetails& details) { 331 switch (type) { 332 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE: 333 case chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN: { 334 OnLoginPromptVisible(); 335 registrar_.RemoveAll(); 336 break; 337 } 338 default: 339 NOTREACHED() << "Unexpected notification " << type; 340 } 341 } 342 343 // WebUILoginView private: ----------------------------------------------------- 344 345 bool WebUILoginView::HandleContextMenu( 346 const content::ContextMenuParams& params) { 347 // Do not show the context menu. 348 #ifndef NDEBUG 349 return false; 350 #else 351 return true; 352 #endif 353 } 354 355 void WebUILoginView::HandleKeyboardEvent(content::WebContents* source, 356 const NativeWebKeyboardEvent& event) { 357 if (forward_keyboard_event_) { 358 // Disable arrow key traversal because arrow keys are handled via 359 // accelerator when this view has focus. 360 ScopedArrowKeyTraversal arrow_key_traversal(false); 361 362 unhandled_keyboard_event_handler_.HandleKeyboardEvent(event, 363 GetFocusManager()); 364 } 365 366 // Make sure error bubble is cleared on keyboard event. This is needed 367 // when the focus is inside an iframe. Only clear on KeyDown to prevent hiding 368 // an immediate authentication error (See crbug.com/103643). 369 if (event.type == blink::WebInputEvent::KeyDown) { 370 content::WebUI* web_ui = GetWebUI(); 371 if (web_ui) 372 web_ui->CallJavascriptFunction("cr.ui.Oobe.clearErrors"); 373 } 374 } 375 376 bool WebUILoginView::IsPopupOrPanel(const WebContents* source) const { 377 return true; 378 } 379 380 bool WebUILoginView::TakeFocus(content::WebContents* source, bool reverse) { 381 // In case of blocked UI (ex.: sign in is in progress) 382 // we should not process focus change events. 383 if (!forward_keyboard_event_) 384 return false; 385 386 ash::SystemTray* tray = ash::Shell::GetInstance()->GetPrimarySystemTray(); 387 if (tray && tray->GetWidget()->IsVisible()) { 388 tray->SetNextFocusableView(this); 389 ash::Shell::GetInstance()->RotateFocus(reverse ? ash::Shell::BACKWARD : 390 ash::Shell::FORWARD); 391 } 392 393 return true; 394 } 395 396 void WebUILoginView::RequestMediaAccessPermission( 397 WebContents* web_contents, 398 const content::MediaStreamRequest& request, 399 const content::MediaResponseCallback& callback) { 400 if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback)) 401 NOTREACHED() << "Media stream not allowed for WebUI"; 402 } 403 404 void WebUILoginView::DidFailProvisionalLoad( 405 int64 frame_id, 406 const base::string16& frame_unique_name, 407 bool is_main_frame, 408 const GURL& validated_url, 409 int error_code, 410 const base::string16& error_description, 411 content::RenderViewHost* render_view_host) { 412 if (frame_unique_name != UTF8ToUTF16("gaia-frame")) 413 return; 414 415 base::FundamentalValue error_value(-error_code); 416 GetWebUI()->CallJavascriptFunction("login.GaiaSigninScreen.onFrameError", 417 error_value); 418 } 419 420 void WebUILoginView::OnLoginPromptVisible() { 421 // If we're hidden than will generate this signal once we're shown. 422 if (is_hidden_ || webui_visible_) { 423 LOG(WARNING) << "Login WebUI >> not emitting signal, hidden: " 424 << is_hidden_; 425 return; 426 } 427 TRACE_EVENT0("chromeos", "WebUILoginView::OnLoginPromoptVisible"); 428 if (should_emit_login_prompt_visible_) { 429 LOG(WARNING) << "Login WebUI >> login-prompt-visible"; 430 chromeos::DBusThreadManager::Get()->GetSessionManagerClient()-> 431 EmitLoginPromptVisible(); 432 } 433 434 webui_visible_ = true; 435 } 436 437 void WebUILoginView::ReturnFocus(bool reverse) { 438 // Return the focus to the web contents. 439 webui_login_->web_contents()->FocusThroughTabTraversal(reverse); 440 GetWidget()->Activate(); 441 } 442 443 } // namespace chromeos 444