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