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