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