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