• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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