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/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_frame_host.h"
37 #include "content/public/browser/render_view_host.h"
38 #include "content/public/browser/render_widget_host_view.h"
39 #include "content/public/browser/web_contents.h"
40 #include "content/public/browser/web_ui.h"
41 #include "third_party/WebKit/public/web/WebInputEvent.h"
42 #include "ui/gfx/rect.h"
43 #include "ui/gfx/size.h"
44 #include "ui/views/controls/webview/webview.h"
45 #include "ui/views/widget/widget.h"
47 using content::NativeWebKeyboardEvent;
48 using content::RenderViewHost;
49 using content::WebContents;
50 using web_modal::WebContentsModalDialogManager;
54 // These strings must be kept in sync with handleAccelerator()
55 // in display_manager.js.
56 const char kAccelNameCancel[] = "cancel";
57 const char kAccelNameEnrollment[] = "enrollment";
58 const char kAccelNameKioskEnable[] = "kiosk_enable";
59 const char kAccelNameVersion[] = "version";
60 const char kAccelNameReset[] = "reset";
61 const char kAccelFocusPrev[] = "focus_prev";
62 const char kAccelFocusNext[] = "focus_next";
63 const char kAccelNameDeviceRequisition[] = "device_requisition";
64 const char kAccelNameDeviceRequisitionRemora[] = "device_requisition_remora";
65 const char kAccelNameDeviceRequisitionShark[] = "device_requisition_shark";
66 const char kAccelNameAppLaunchBailout[] = "app_launch_bailout";
67 const char kAccelNameAppLaunchNetworkConfig[] = "app_launch_network_config";
69 // A class to change arrow key traversal behavior when it's alive.
70 class ScopedArrowKeyTraversal {
72 explicit ScopedArrowKeyTraversal(bool new_arrow_key_tranversal_enabled)
73 : previous_arrow_key_traversal_enabled_(
74 views::FocusManager::arrow_key_traversal_enabled()) {
75 views::FocusManager::set_arrow_key_traversal_enabled(
76 new_arrow_key_tranversal_enabled);
78 ~ScopedArrowKeyTraversal() {
79 views::FocusManager::set_arrow_key_traversal_enabled(
80 previous_arrow_key_traversal_enabled_);
84 const bool previous_arrow_key_traversal_enabled_;
85 DISALLOW_COPY_AND_ASSIGN(ScopedArrowKeyTraversal);
93 const char WebUILoginView::kViewClassName[] =
94 "browser/chromeos/login/WebUILoginView";
96 // WebUILoginView public: ------------------------------------------------------
98 WebUILoginView::WebUILoginView()
101 webui_visible_(false),
102 should_emit_login_prompt_visible_(true),
103 forward_keyboard_event_(true) {
105 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
106 content::NotificationService::AllSources());
108 chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
109 content::NotificationService::AllSources());
111 accel_map_[ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)] =
113 accel_map_[ui::Accelerator(ui::VKEY_E,
114 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
115 kAccelNameEnrollment;
116 accel_map_[ui::Accelerator(ui::VKEY_K,
117 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
118 kAccelNameKioskEnable;
119 accel_map_[ui::Accelerator(ui::VKEY_V, ui::EF_ALT_DOWN)] =
121 accel_map_[ui::Accelerator(ui::VKEY_R,
122 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)] =
125 accel_map_[ui::Accelerator(ui::VKEY_LEFT, ui::EF_NONE)] =
127 accel_map_[ui::Accelerator(ui::VKEY_RIGHT, ui::EF_NONE)] =
130 // Use KEY_RELEASED because Gaia consumes KEY_PRESSED for up/down key.
131 ui::Accelerator key_up(ui::VKEY_UP, ui::EF_NONE);
132 key_up.set_type(ui::ET_KEY_RELEASED);
133 ui::Accelerator key_down(ui::VKEY_DOWN, ui::EF_NONE);
134 key_down.set_type(ui::ET_KEY_RELEASED);
135 accel_map_[key_up] = kAccelFocusPrev;
136 accel_map_[key_down] = kAccelFocusNext;
138 accel_map_[ui::Accelerator(
139 ui::VKEY_D, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)] =
140 kAccelNameDeviceRequisition;
142 ui::Accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
143 kAccelNameDeviceRequisitionRemora;
145 ui::Accelerator(ui::VKEY_H,
146 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)] =
147 kAccelNameDeviceRequisitionShark;
149 accel_map_[ui::Accelerator(ui::VKEY_S,
150 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
151 kAccelNameAppLaunchBailout;
153 accel_map_[ui::Accelerator(ui::VKEY_N,
154 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)] =
155 kAccelNameAppLaunchNetworkConfig;
157 for (AccelMap::iterator i(accel_map_.begin()); i != accel_map_.end(); ++i)
158 AddAccelerator(i->first);
161 WebUILoginView::~WebUILoginView() {
162 FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver,
166 if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) {
167 ash::Shell::GetInstance()->GetPrimarySystemTray()->
168 SetNextFocusableView(NULL);
172 void WebUILoginView::Init() {
173 Profile* signin_profile = ProfileHelper::GetSigninProfile();
174 auth_extension_.reset(new ScopedGaiaAuthExtension(signin_profile));
175 webui_login_ = new views::WebView(signin_profile);
176 webui_login_->set_allow_accelerators(true);
177 AddChildView(webui_login_);
179 WebContents* web_contents = webui_login_->GetWebContents();
181 // Ensure that the login UI has a tab ID, which will allow the GAIA auth
182 // extension's background script to tell it apart from a captive portal window
183 // that may be opened on top of this UI.
184 SessionTabHelper::CreateForWebContents(web_contents);
186 // Create the password manager that is needed for the proxy.
187 ChromePasswordManagerClient::CreateForWebContentsWithAutofillClient(
189 autofill::ChromeAutofillClient::FromWebContents(web_contents));
191 // LoginHandlerViews uses a constrained window for the password manager view.
192 WebContentsModalDialogManager::CreateForWebContents(web_contents);
193 WebContentsModalDialogManager::FromWebContents(web_contents)->
195 if (!popup_manager_.get())
196 popup_manager_.reset(new web_modal::PopupManager(this));
197 popup_manager_->RegisterWith(web_contents);
199 web_contents->SetDelegate(this);
200 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
202 WebContentsObserver::Observe(web_contents);
203 renderer_preferences_util::UpdateFromSystemSettings(
204 web_contents->GetMutableRendererPrefs(),
208 const char* WebUILoginView::GetClassName() const {
209 return kViewClassName;
212 void WebUILoginView::RequestFocus() {
213 webui_login_->RequestFocus();
216 web_modal::WebContentsModalDialogHost*
217 WebUILoginView::GetWebContentsModalDialogHost() {
221 gfx::NativeView WebUILoginView::GetHostView() const {
222 return GetWidget()->GetNativeView();
225 gfx::Point WebUILoginView::GetDialogPosition(const gfx::Size& size) {
226 // Center the widget.
227 gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size();
228 return gfx::Point(widget_size.width() / 2 - size.width() / 2,
229 widget_size.height() / 2 - size.height() / 2);
232 gfx::Size WebUILoginView::GetMaximumDialogSize() {
233 return GetWidget()->GetWindowBoundsInScreen().size();
236 void WebUILoginView::AddObserver(
237 web_modal::ModalDialogHostObserver* observer) {
238 if (observer && !observer_list_.HasObserver(observer))
239 observer_list_.AddObserver(observer);
242 void WebUILoginView::RemoveObserver(
243 web_modal::ModalDialogHostObserver* observer) {
244 observer_list_.RemoveObserver(observer);
247 bool WebUILoginView::AcceleratorPressed(
248 const ui::Accelerator& accelerator) {
249 AccelMap::const_iterator entry = accel_map_.find(accelerator);
250 if (entry == accel_map_.end())
256 content::WebUI* web_ui = GetWebUI();
258 base::StringValue accel_name(entry->second);
259 web_ui->CallJavascriptFunction("cr.ui.Oobe.handleAccelerator",
266 gfx::NativeWindow WebUILoginView::GetNativeWindow() const {
267 return GetWidget()->GetNativeWindow();
270 void WebUILoginView::LoadURL(const GURL & url) {
271 webui_login_->LoadInitialURL(url);
272 webui_login_->RequestFocus();
274 // TODO(nkostylev): Use WebContentsObserver::RenderViewCreated to track
275 // when RenderView is created.
276 GetWebContents()->GetRenderViewHost()->GetView()->SetBackgroundOpaque(false);
279 content::WebUI* WebUILoginView::GetWebUI() {
280 return webui_login_->web_contents()->GetWebUI();
283 content::WebContents* WebUILoginView::GetWebContents() {
284 return webui_login_->web_contents();
287 void WebUILoginView::OpenProxySettings() {
288 const NetworkState* network =
289 NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
291 LOG(ERROR) << "No default network found!";
294 ProxySettingsDialog* dialog =
295 new ProxySettingsDialog(ProfileHelper::GetSigninProfile(),
296 *network, NULL, GetNativeWindow());
300 void WebUILoginView::OnPostponedShow() {
301 set_is_hidden(false);
302 OnLoginPromptVisible();
305 void WebUILoginView::SetStatusAreaVisible(bool visible) {
306 if (ash::Shell::GetInstance()->HasPrimaryStatusArea()) {
307 ash::SystemTray* tray = ash::Shell::GetInstance()->GetPrimarySystemTray();
309 // Tray may have been initialized being hidden.
310 tray->SetVisible(visible);
311 tray->GetWidget()->Show();
313 tray->GetWidget()->Hide();
318 void WebUILoginView::SetUIEnabled(bool enabled) {
319 forward_keyboard_event_ = enabled;
320 ash::Shell::GetInstance()->GetPrimarySystemTray()->SetEnabled(enabled);
323 void WebUILoginView::AddFrameObserver(FrameObserver* frame_observer) {
324 DCHECK(frame_observer);
325 DCHECK(!frame_observer_list_.HasObserver(frame_observer));
326 frame_observer_list_.AddObserver(frame_observer);
329 void WebUILoginView::RemoveFrameObserver(FrameObserver* frame_observer) {
330 DCHECK(frame_observer);
331 DCHECK(frame_observer_list_.HasObserver(frame_observer));
332 frame_observer_list_.RemoveObserver(frame_observer);
335 // WebUILoginView protected: ---------------------------------------------------
337 void WebUILoginView::Layout() {
338 DCHECK(webui_login_);
339 webui_login_->SetBoundsRect(bounds());
341 FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver,
343 OnPositionRequiresUpdate());
346 void WebUILoginView::OnLocaleChanged() {
349 void WebUILoginView::ChildPreferredSizeChanged(View* child) {
354 void WebUILoginView::AboutToRequestFocusFromTabTraversal(bool reverse) {
355 // Return the focus to the web contents.
356 webui_login_->web_contents()->FocusThroughTabTraversal(reverse);
357 GetWidget()->Activate();
358 webui_login_->web_contents()->Focus();
361 void WebUILoginView::Observe(int type,
362 const content::NotificationSource& source,
363 const content::NotificationDetails& details) {
365 case chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE:
366 case chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN: {
367 OnLoginPromptVisible();
368 registrar_.RemoveAll();
372 NOTREACHED() << "Unexpected notification " << type;
376 // WebUILoginView private: -----------------------------------------------------
378 bool WebUILoginView::HandleContextMenu(
379 const content::ContextMenuParams& params) {
380 // Do not show the context menu.
388 void WebUILoginView::HandleKeyboardEvent(content::WebContents* source,
389 const NativeWebKeyboardEvent& event) {
390 if (forward_keyboard_event_) {
391 // Disable arrow key traversal because arrow keys are handled via
392 // accelerator when this view has focus.
393 ScopedArrowKeyTraversal arrow_key_traversal(false);
395 unhandled_keyboard_event_handler_.HandleKeyboardEvent(event,
399 // Make sure error bubble is cleared on keyboard event. This is needed
400 // when the focus is inside an iframe. Only clear on KeyDown to prevent hiding
401 // an immediate authentication error (See crbug.com/103643).
402 if (event.type == blink::WebInputEvent::KeyDown) {
403 content::WebUI* web_ui = GetWebUI();
405 web_ui->CallJavascriptFunction("cr.ui.Oobe.clearErrors");
409 bool WebUILoginView::IsPopupOrPanel(const WebContents* source) const {
413 bool WebUILoginView::TakeFocus(content::WebContents* source, bool reverse) {
414 // In case of blocked UI (ex.: sign in is in progress)
415 // we should not process focus change events.
416 if (!forward_keyboard_event_)
419 ash::SystemTray* tray = ash::Shell::GetInstance()->GetPrimarySystemTray();
420 if (tray && tray->GetWidget()->IsVisible()) {
421 tray->SetNextFocusableView(this);
422 ash::Shell::GetInstance()->RotateFocus(reverse ? ash::Shell::BACKWARD :
423 ash::Shell::FORWARD);
429 void WebUILoginView::RequestMediaAccessPermission(
430 WebContents* web_contents,
431 const content::MediaStreamRequest& request,
432 const content::MediaResponseCallback& callback) {
433 if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback))
434 NOTREACHED() << "Media stream not allowed for WebUI";
437 bool WebUILoginView::PreHandleGestureEvent(
438 content::WebContents* source,
439 const blink::WebGestureEvent& event) {
440 // Disable pinch zooming.
441 return event.type == blink::WebGestureEvent::GesturePinchBegin ||
442 event.type == blink::WebGestureEvent::GesturePinchUpdate ||
443 event.type == blink::WebGestureEvent::GesturePinchEnd;
446 void WebUILoginView::DidFailProvisionalLoad(
447 content::RenderFrameHost* render_frame_host,
448 const GURL& validated_url,
450 const base::string16& error_description) {
451 FOR_EACH_OBSERVER(FrameObserver,
452 frame_observer_list_,
453 OnFrameError(render_frame_host->GetFrameName()));
454 if (render_frame_host->GetFrameName() != "gaia-frame")
457 GetWebUI()->CallJavascriptFunction("login.GaiaSigninScreen.onFrameError",
458 base::FundamentalValue(-error_code),
459 base::StringValue(validated_url.spec()));
462 void WebUILoginView::OnLoginPromptVisible() {
463 // If we're hidden than will generate this signal once we're shown.
464 if (is_hidden_ || webui_visible_) {
465 VLOG(1) << "Login WebUI >> not emitting signal, hidden: " << is_hidden_;
468 TRACE_EVENT0("chromeos", "WebUILoginView::OnLoginPromoptVisible");
469 if (should_emit_login_prompt_visible_) {
470 VLOG(1) << "Login WebUI >> login-prompt-visible";
471 chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->
472 EmitLoginPromptVisible();
475 webui_visible_ = true;
478 void WebUILoginView::ReturnFocus(bool reverse) {
479 // Return the focus to the web contents.
480 webui_login_->web_contents()->FocusThroughTabTraversal(reverse);
481 GetWidget()->Activate();
484 } // namespace chromeos