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.
5 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
8 #include "ash/system/tray/system_tray.h"
10 #include "base/callback.h"
11 #include "base/i18n/rtl.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/trace_event/trace_event.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_capture_devices_dispatcher.h"
23 #include "chrome/browser/media/media_stream_infobar_delegate.h"
24 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
25 #include "chrome/browser/renderer_preferences_util.h"
26 #include "chrome/browser/sessions/session_tab_helper.h"
27 #include "chrome/browser/ui/autofill/chrome_autofill_client.h"
28 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
29 #include "chrome/common/render_messages.h"
30 #include "chromeos/dbus/dbus_thread_manager.h"
31 #include "chromeos/dbus/session_manager_client.h"
32 #include "chromeos/network/network_state.h"
33 #include "chromeos/network/network_state_handler.h"
34 #include "components/password_manager/core/browser/password_manager.h"
35 #include "components/web_modal/web_contents_modal_dialog_manager.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/render_frame_host.h"
38 #include "content/public/browser/render_view_host.h"
39 #include "content/public/browser/render_widget_host_view.h"
40 #include "content/public/browser/web_contents.h"
41 #include "content/public/browser/web_ui.h"
42 #include "content/public/common/renderer_preferences.h"
43 #include "third_party/WebKit/public/web/WebInputEvent.h"
44 #include "ui/gfx/geometry/rect.h"
45 #include "ui/gfx/geometry/size.h"
46 #include "ui/views/controls/webview/webview.h"
47 #include "ui/views/widget/widget.h"
49 using content::NativeWebKeyboardEvent
;
50 using content::RenderViewHost
;
51 using content::WebContents
;
52 using web_modal::WebContentsModalDialogManager
;
56 // These strings must be kept in sync with handleAccelerator()
57 // in display_manager.js.
58 const char kAccelNameCancel
[] = "cancel";
59 const char kAccelNameEnableDebugging
[] = "debugging";
60 const char kAccelNameEnrollment
[] = "enrollment";
61 const char kAccelNameKioskEnable
[] = "kiosk_enable";
62 const char kAccelNameVersion
[] = "version";
63 const char kAccelNameReset
[] = "reset";
64 const char kAccelFocusPrev
[] = "focus_prev";
65 const char kAccelFocusNext
[] = "focus_next";
66 const char kAccelNameDeviceRequisition
[] = "device_requisition";
67 const char kAccelNameDeviceRequisitionRemora
[] = "device_requisition_remora";
68 const char kAccelNameDeviceRequisitionShark
[] = "device_requisition_shark";
69 const char kAccelNameAppLaunchBailout
[] = "app_launch_bailout";
70 const char kAccelNameAppLaunchNetworkConfig
[] = "app_launch_network_config";
71 const char kAccelNameEmbeddedSignin
[] = "embedded_signin";
72 const char kAccelNameNewOobe
[] = "new_oobe";
73 const char kAccelNameToggleWebviewSignin
[] = "toggle_webview_signin";
75 // A class to change arrow key traversal behavior when it's alive.
76 class ScopedArrowKeyTraversal
{
78 explicit ScopedArrowKeyTraversal(bool new_arrow_key_tranversal_enabled
)
79 : previous_arrow_key_traversal_enabled_(
80 views::FocusManager::arrow_key_traversal_enabled()) {
81 views::FocusManager::set_arrow_key_traversal_enabled(
82 new_arrow_key_tranversal_enabled
);
84 ~ScopedArrowKeyTraversal() {
85 views::FocusManager::set_arrow_key_traversal_enabled(
86 previous_arrow_key_traversal_enabled_
);
90 const bool previous_arrow_key_traversal_enabled_
;
91 DISALLOW_COPY_AND_ASSIGN(ScopedArrowKeyTraversal
);
99 const char WebUILoginView::kViewClassName
[] =
100 "browser/chromeos/login/WebUILoginView";
102 // WebUILoginView public: ------------------------------------------------------
104 WebUILoginView::WebUILoginView()
105 : webui_login_(NULL
),
107 webui_visible_(false),
108 should_emit_login_prompt_visible_(true),
109 forward_keyboard_event_(true) {
111 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE
,
112 content::NotificationService::AllSources());
114 chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN
,
115 content::NotificationService::AllSources());
117 accel_map_
[ui::Accelerator(ui::VKEY_ESCAPE
, ui::EF_NONE
)] =
119 accel_map_
[ui::Accelerator(ui::VKEY_E
,
120 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
)] =
121 kAccelNameEnrollment
;
122 accel_map_
[ui::Accelerator(
123 ui::VKEY_G
, ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
)] =
124 kAccelNameEmbeddedSignin
;
125 accel_map_
[ui::Accelerator(ui::VKEY_K
,
126 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
)] =
127 kAccelNameKioskEnable
;
128 accel_map_
[ui::Accelerator(ui::VKEY_V
, ui::EF_ALT_DOWN
)] =
130 accel_map_
[ui::Accelerator(ui::VKEY_R
,
131 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
)] =
133 accel_map_
[ui::Accelerator(ui::VKEY_X
,
134 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
)] =
135 kAccelNameEnableDebugging
;
136 accel_map_
[ui::Accelerator(ui::VKEY_W
,
137 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
)] =
138 kAccelNameToggleWebviewSignin
;
140 accel_map_
[ui::Accelerator(ui::VKEY_LEFT
, ui::EF_NONE
)] =
142 accel_map_
[ui::Accelerator(ui::VKEY_RIGHT
, ui::EF_NONE
)] =
145 // Use KEY_RELEASED because Gaia consumes KEY_PRESSED for up/down key.
146 ui::Accelerator
key_up(ui::VKEY_UP
, ui::EF_NONE
);
147 key_up
.set_type(ui::ET_KEY_RELEASED
);
148 ui::Accelerator
key_down(ui::VKEY_DOWN
, ui::EF_NONE
);
149 key_down
.set_type(ui::ET_KEY_RELEASED
);
150 accel_map_
[key_up
] = kAccelFocusPrev
;
151 accel_map_
[key_down
] = kAccelFocusNext
;
153 accel_map_
[ui::Accelerator(
154 ui::VKEY_D
, ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
)] =
155 kAccelNameDeviceRequisition
;
157 ui::Accelerator(ui::VKEY_H
, ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
)] =
158 kAccelNameDeviceRequisitionRemora
;
160 ui::Accelerator(ui::VKEY_H
,
161 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
| ui::EF_SHIFT_DOWN
)] =
162 kAccelNameDeviceRequisitionShark
;
164 accel_map_
[ui::Accelerator(ui::VKEY_S
,
165 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
)] =
166 kAccelNameAppLaunchBailout
;
168 accel_map_
[ui::Accelerator(ui::VKEY_N
,
169 ui::EF_CONTROL_DOWN
| ui::EF_ALT_DOWN
)] =
170 kAccelNameAppLaunchNetworkConfig
;
172 accel_map_
[ui::Accelerator(
173 ui::VKEY_O
, ui::EF_CONTROL_DOWN
| ui::EF_SHIFT_DOWN
| ui::EF_ALT_DOWN
)] =
176 for (AccelMap::iterator
i(accel_map_
.begin()); i
!= accel_map_
.end(); ++i
)
177 AddAccelerator(i
->first
);
180 WebUILoginView::~WebUILoginView() {
181 FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver
,
185 if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) {
186 ash::Shell::GetInstance()->GetPrimarySystemTray()->
187 SetNextFocusableView(NULL
);
191 void WebUILoginView::Init() {
192 Profile
* signin_profile
= ProfileHelper::GetSigninProfile();
193 webui_login_
= new views::WebView(signin_profile
);
194 webui_login_
->set_allow_accelerators(true);
195 AddChildView(webui_login_
);
197 WebContents
* web_contents
= webui_login_
->GetWebContents();
199 // Ensure that the login UI has a tab ID, which will allow the GAIA auth
200 // extension's background script to tell it apart from a captive portal window
201 // that may be opened on top of this UI.
202 SessionTabHelper::CreateForWebContents(web_contents
);
204 // Create the password manager that is needed for the proxy.
205 ChromePasswordManagerClient::CreateForWebContentsWithAutofillClient(
207 autofill::ChromeAutofillClient::FromWebContents(web_contents
));
209 // LoginHandlerViews uses a constrained window for the password manager view.
210 WebContentsModalDialogManager::CreateForWebContents(web_contents
);
211 WebContentsModalDialogManager::FromWebContents(web_contents
)->
213 if (!popup_manager_
.get())
214 popup_manager_
.reset(new web_modal::PopupManager(this));
215 popup_manager_
->RegisterWith(web_contents
);
217 web_contents
->SetDelegate(this);
218 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
220 WebContentsObserver::Observe(web_contents
);
221 content::RendererPreferences
* prefs
= web_contents
->GetMutableRendererPrefs();
222 renderer_preferences_util::UpdateFromSystemSettings(
223 prefs
, signin_profile
, web_contents
);
226 const char* WebUILoginView::GetClassName() const {
227 return kViewClassName
;
230 void WebUILoginView::RequestFocus() {
231 webui_login_
->RequestFocus();
234 web_modal::WebContentsModalDialogHost
*
235 WebUILoginView::GetWebContentsModalDialogHost() {
239 gfx::NativeView
WebUILoginView::GetHostView() const {
240 return GetWidget()->GetNativeView();
243 gfx::Point
WebUILoginView::GetDialogPosition(const gfx::Size
& size
) {
244 // Center the widget.
245 gfx::Size widget_size
= GetWidget()->GetWindowBoundsInScreen().size();
246 return gfx::Point(widget_size
.width() / 2 - size
.width() / 2,
247 widget_size
.height() / 2 - size
.height() / 2);
250 gfx::Size
WebUILoginView::GetMaximumDialogSize() {
251 return GetWidget()->GetWindowBoundsInScreen().size();
254 void WebUILoginView::AddObserver(
255 web_modal::ModalDialogHostObserver
* observer
) {
256 if (observer
&& !observer_list_
.HasObserver(observer
))
257 observer_list_
.AddObserver(observer
);
260 void WebUILoginView::RemoveObserver(
261 web_modal::ModalDialogHostObserver
* observer
) {
262 observer_list_
.RemoveObserver(observer
);
265 bool WebUILoginView::AcceleratorPressed(
266 const ui::Accelerator
& accelerator
) {
267 AccelMap::const_iterator entry
= accel_map_
.find(accelerator
);
268 if (entry
== accel_map_
.end())
274 content::WebUI
* web_ui
= GetWebUI();
276 base::StringValue
accel_name(entry
->second
);
277 web_ui
->CallJavascriptFunction("cr.ui.Oobe.handleAccelerator",
284 gfx::NativeWindow
WebUILoginView::GetNativeWindow() const {
285 return GetWidget()->GetNativeWindow();
288 void WebUILoginView::LoadURL(const GURL
& url
) {
289 webui_login_
->LoadInitialURL(url
);
290 webui_login_
->RequestFocus();
292 // TODO(nkostylev): Use WebContentsObserver::RenderViewCreated to track
293 // when RenderView is created.
294 GetWebContents()->GetRenderViewHost()->GetView()->SetBackgroundColor(
295 SK_ColorTRANSPARENT
);
298 content::WebUI
* WebUILoginView::GetWebUI() {
299 return webui_login_
->web_contents()->GetWebUI();
302 content::WebContents
* WebUILoginView::GetWebContents() {
303 return webui_login_
->web_contents();
306 void WebUILoginView::OpenProxySettings() {
307 const NetworkState
* network
=
308 NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
310 LOG(ERROR
) << "No default network found!";
313 ProxySettingsDialog
* dialog
=
314 new ProxySettingsDialog(ProfileHelper::GetSigninProfile(),
315 *network
, NULL
, GetNativeWindow());
319 void WebUILoginView::OnPostponedShow() {
320 set_is_hidden(false);
321 OnLoginPromptVisible();
324 void WebUILoginView::SetStatusAreaVisible(bool visible
) {
325 if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) {
326 ash::SystemTray
* tray
= ash::Shell::GetInstance()->GetPrimarySystemTray();
328 // Tray may have been initialized being hidden.
329 tray
->SetVisible(visible
);
330 tray
->GetWidget()->Show();
332 tray
->GetWidget()->Hide();
337 void WebUILoginView::SetUIEnabled(bool enabled
) {
338 forward_keyboard_event_
= enabled
;
339 ash::Shell::GetInstance()->GetPrimarySystemTray()->SetEnabled(enabled
);
342 void WebUILoginView::AddFrameObserver(FrameObserver
* frame_observer
) {
343 DCHECK(frame_observer
);
344 DCHECK(!frame_observer_list_
.HasObserver(frame_observer
));
345 frame_observer_list_
.AddObserver(frame_observer
);
348 void WebUILoginView::RemoveFrameObserver(FrameObserver
* frame_observer
) {
349 DCHECK(frame_observer
);
350 DCHECK(frame_observer_list_
.HasObserver(frame_observer
));
351 frame_observer_list_
.RemoveObserver(frame_observer
);
354 // WebUILoginView protected: ---------------------------------------------------
356 void WebUILoginView::Layout() {
357 DCHECK(webui_login_
);
358 webui_login_
->SetBoundsRect(bounds());
360 FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver
,
362 OnPositionRequiresUpdate());
365 void WebUILoginView::OnLocaleChanged() {
368 void WebUILoginView::ChildPreferredSizeChanged(View
* child
) {
373 void WebUILoginView::AboutToRequestFocusFromTabTraversal(bool reverse
) {
374 // Return the focus to the web contents.
375 webui_login_
->web_contents()->FocusThroughTabTraversal(reverse
);
376 GetWidget()->Activate();
377 webui_login_
->web_contents()->Focus();
380 void WebUILoginView::Observe(int type
,
381 const content::NotificationSource
& source
,
382 const content::NotificationDetails
& details
) {
384 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE
:
385 case chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN
: {
386 OnLoginPromptVisible();
387 registrar_
.RemoveAll();
391 NOTREACHED() << "Unexpected notification " << type
;
395 // WebUILoginView private: -----------------------------------------------------
397 bool WebUILoginView::HandleContextMenu(
398 const content::ContextMenuParams
& params
) {
399 // Do not show the context menu.
407 void WebUILoginView::HandleKeyboardEvent(content::WebContents
* source
,
408 const NativeWebKeyboardEvent
& event
) {
409 if (forward_keyboard_event_
) {
410 // Disable arrow key traversal because arrow keys are handled via
411 // accelerator when this view has focus.
412 ScopedArrowKeyTraversal
arrow_key_traversal(false);
414 unhandled_keyboard_event_handler_
.HandleKeyboardEvent(event
,
418 // Make sure error bubble is cleared on keyboard event. This is needed
419 // when the focus is inside an iframe. Only clear on KeyDown to prevent hiding
420 // an immediate authentication error (See crbug.com/103643).
421 if (event
.type
== blink::WebInputEvent::KeyDown
) {
422 content::WebUI
* web_ui
= GetWebUI();
424 web_ui
->CallJavascriptFunction("cr.ui.Oobe.clearErrors");
428 bool WebUILoginView::IsPopupOrPanel(const WebContents
* source
) const {
432 bool WebUILoginView::TakeFocus(content::WebContents
* source
, bool reverse
) {
433 // In case of blocked UI (ex.: sign in is in progress)
434 // we should not process focus change events.
435 if (!forward_keyboard_event_
)
438 ash::SystemTray
* tray
= ash::Shell::GetInstance()->GetPrimarySystemTray();
439 if (tray
&& tray
->GetWidget()->IsVisible()) {
440 tray
->SetNextFocusableView(this);
441 ash::Shell::GetInstance()->RotateFocus(reverse
? ash::Shell::BACKWARD
:
442 ash::Shell::FORWARD
);
448 void WebUILoginView::RequestMediaAccessPermission(
449 WebContents
* web_contents
,
450 const content::MediaStreamRequest
& request
,
451 const content::MediaResponseCallback
& callback
) {
452 if (MediaStreamInfoBarDelegate::Create(web_contents
, request
, callback
))
453 NOTREACHED() << "Media stream not allowed for WebUI";
456 bool WebUILoginView::CheckMediaAccessPermission(
457 content::WebContents
* web_contents
,
458 const GURL
& security_origin
,
459 content::MediaStreamType type
) {
460 return MediaCaptureDevicesDispatcher::GetInstance()
461 ->CheckMediaAccessPermission(web_contents
, security_origin
, type
);
464 bool WebUILoginView::PreHandleGestureEvent(
465 content::WebContents
* source
,
466 const blink::WebGestureEvent
& event
) {
467 // Disable pinch zooming.
468 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
469 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
470 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
473 void WebUILoginView::DidFailProvisionalLoad(
474 content::RenderFrameHost
* render_frame_host
,
475 const GURL
& validated_url
,
477 const base::string16
& error_description
) {
478 FOR_EACH_OBSERVER(FrameObserver
,
479 frame_observer_list_
,
480 OnFrameError(render_frame_host
->GetFrameName()));
481 if (render_frame_host
->GetFrameName() != "gaia-frame")
484 GetWebUI()->CallJavascriptFunction("login.GaiaSigninScreen.onFrameError",
485 base::FundamentalValue(-error_code
),
486 base::StringValue(validated_url
.spec()));
489 void WebUILoginView::OnLoginPromptVisible() {
490 // If we're hidden than will generate this signal once we're shown.
491 if (is_hidden_
|| webui_visible_
) {
492 VLOG(1) << "Login WebUI >> not emitting signal, hidden: " << is_hidden_
;
495 TRACE_EVENT0("chromeos", "WebUILoginView::OnLoginPromoptVisible");
496 if (should_emit_login_prompt_visible_
) {
497 VLOG(1) << "Login WebUI >> login-prompt-visible";
498 chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->
499 EmitLoginPromptVisible();
502 webui_visible_
= true;
505 void WebUILoginView::ReturnFocus(bool reverse
) {
506 // Return the focus to the web contents.
507 webui_login_
->web_contents()->FocusThroughTabTraversal(reverse
);
508 GetWidget()->Activate();
511 } // namespace chromeos