Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(94)

Side by Side Diff: chrome/browser/chromeos/login/webui_screen_locker.cc

Issue 8573004: Grab inputs on WebUI screen locker. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Move grab to WebUIScreenLocker. Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/chromeos/login/webui_screen_locker.h" 5 #include "chrome/browser/chromeos/login/webui_screen_locker.h"
6 6
7 #include <X11/extensions/XTest.h> 7 #include <X11/extensions/XTest.h>
8 #include <X11/keysym.h> 8 #include <X11/keysym.h>
9 #include <gdk/gdkkeysyms.h> 9 #include <gdk/gdkkeysyms.h>
10 #include <gdk/gdkx.h> 10 #include <gdk/gdkx.h>
11 11
12 // Evil hack to undo X11 evil #define. 12 // Evil hack to undo X11 evil #define.
13 #undef None 13 #undef None
14 #undef Status 14 #undef Status
15 15
16 #include "base/command_line.h" 16 #include "base/command_line.h"
17 #include "base/utf_string_conversions.h" 17 #include "base/utf_string_conversions.h"
18 #include "base/values.h" 18 #include "base/values.h"
19 #include "chrome/browser/chromeos/cros/cros_library.h" 19 #include "chrome/browser/chromeos/cros/cros_library.h"
20 #include "chrome/browser/chromeos/cros/network_library.h" 20 #include "chrome/browser/chromeos/cros/network_library.h"
21 #include "chrome/browser/chromeos/legacy_window_manager/wm_ipc.h" 21 #include "chrome/browser/chromeos/legacy_window_manager/wm_ipc.h"
22 #include "chrome/browser/chromeos/login/helper.h" 22 #include "chrome/browser/chromeos/login/helper.h"
23 #include "chrome/browser/chromeos/login/screen_locker.h" 23 #include "chrome/browser/chromeos/login/screen_locker.h"
24 #include "chrome/browser/chromeos/login/user_manager.h" 24 #include "chrome/browser/chromeos/login/user_manager.h"
25 #include "chrome/browser/chromeos/login/webui_login_display.h" 25 #include "chrome/browser/chromeos/login/webui_login_display.h"
26 #include "chrome/browser/ui/views/dom_view.h"
26 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h" 27 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
27 #include "chrome/common/chrome_notification_types.h" 28 #include "chrome/common/chrome_notification_types.h"
28 #include "chrome/common/url_constants.h" 29 #include "chrome/common/url_constants.h"
30 #include "content/browser/renderer_host/render_widget_host_view.h"
29 #include "content/public/browser/notification_service.h" 31 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/notification_types.h" 32 #include "content/public/browser/notification_types.h"
31 #include "ui/base/l10n/l10n_util.h" 33 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/x/x11_util.h" 34 #include "ui/base/x/x11_util.h"
33 #include "ui/gfx/screen.h" 35 #include "ui/gfx/screen.h"
34 #include "views/widget/native_widget_gtk.h" 36 #include "views/widget/native_widget_gtk.h"
35 37
36 namespace { 38 namespace {
37 39
38 // URL which corresponds to the login WebUI. 40 // URL which corresponds to the login WebUI.
39 const char kLoginURL[] = "chrome://oobe/login"; 41 const char kLoginURL[] = "chrome://oobe/login";
40 42
43 // The maximum duration for which locker should try to grab the keyboard and
44 // mouse and its interval for regrabbing on failure.
45 const int kMaxGrabFailureSec = 30;
46 const int64 kRetryGrabIntervalMs = 500;
47
48 // Maximum number of times we'll try to grab the keyboard and mouse before
49 // giving up. If we hit the limit, Chrome exits and the session is terminated.
50 const int kMaxGrabFailures = kMaxGrabFailureSec * 1000 / kRetryGrabIntervalMs;
51
41 // A ScreenLock window that covers entire screen to keep the keyboard 52 // A ScreenLock window that covers entire screen to keep the keyboard
42 // focus/events inside the grab widget. 53 // focus/events inside the grab widget.
43 class LockWindow : public views::NativeWidgetGtk { 54 class LockWindow : public views::NativeWidgetGtk {
44 public: 55 public:
45 LockWindow() 56 explicit LockWindow(chromeos::WebUIScreenLocker* webui_screen_locker)
46 : views::NativeWidgetGtk(new views::Widget), 57 : views::NativeWidgetGtk(new views::Widget),
47 toplevel_focus_widget_(NULL) { 58 toplevel_focus_widget_(NULL),
59 webui_screen_locker_(webui_screen_locker) {
48 EnableDoubleBuffer(true); 60 EnableDoubleBuffer(true);
49 } 61 }
50 62
51 // GTK propagates key events from parents to children.
52 // Make sure LockWindow will never handle key events.
53 virtual gboolean OnEventKey(GtkWidget* widget, GdkEventKey* event) OVERRIDE {
54 // Don't handle key event in the lock window.
55 return false;
56 }
57
58 virtual gboolean OnButtonPress(GtkWidget* widget, 63 virtual gboolean OnButtonPress(GtkWidget* widget,
59 GdkEventButton* event) OVERRIDE { 64 GdkEventButton* event) OVERRIDE {
60 // Don't handle mouse event in the lock wnidow and 65 // Never propagate mouse events to parent.
61 // nor propagate to child.
62 return true; 66 return true;
63 } 67 }
64 68
65 virtual void OnDestroy(GtkWidget* object) OVERRIDE { 69 virtual void OnDestroy(GtkWidget* object) OVERRIDE {
66 VLOG(1) << "OnDestroy: LockWindow destroyed"; 70 VLOG(1) << "OnDestroy: LockWindow destroyed";
67 views::NativeWidgetGtk::OnDestroy(object); 71 views::NativeWidgetGtk::OnDestroy(object);
68 } 72 }
69 73
70 virtual void ClearNativeFocus() OVERRIDE { 74 virtual void ClearNativeFocus() OVERRIDE {
71 DCHECK(toplevel_focus_widget_); 75 DCHECK(toplevel_focus_widget_);
72 gtk_widget_grab_focus(toplevel_focus_widget_); 76 gtk_widget_grab_focus(toplevel_focus_widget_);
73 } 77 }
74 78
79 virtual void HandleGtkGrabBroke() OVERRIDE {
80 webui_screen_locker_->HandleGtkGrabBroke();
81 }
82
75 // Sets the widget to move the focus to when clearning the native 83 // Sets the widget to move the focus to when clearning the native
76 // widget's focus. 84 // widget's focus.
77 void set_toplevel_focus_widget(GtkWidget* widget) { 85 void set_toplevel_focus_widget(GtkWidget* widget) {
78 gtk_widget_set_can_focus(widget, TRUE); 86 gtk_widget_set_can_focus(widget, TRUE);
79 toplevel_focus_widget_ = widget; 87 toplevel_focus_widget_ = widget;
80 } 88 }
81 89
82 private: 90 private:
83 // The widget we set focus to when clearning the focus on native 91 // The widget we set focus to when clearning the focus on native
84 // widget. In screen locker, gdk input is grabbed in GrabWidget, 92 // widget. Gdk input is grabbed in WebUIScreenLocker, and resetting the focus
85 // and resetting the focus by using gtk_window_set_focus seems to 93 // by using gtk_window_set_focus seems to confuse gtk and doesn't let focus
86 // confuse gtk and doesn't let focus move to native widget under 94 // move to native widget under this.
87 // GrabWidget.
88 GtkWidget* toplevel_focus_widget_; 95 GtkWidget* toplevel_focus_widget_;
89 96
97 // The WebUI screen locker.
98 chromeos::WebUIScreenLocker* webui_screen_locker_;
99
90 DISALLOW_COPY_AND_ASSIGN(LockWindow); 100 DISALLOW_COPY_AND_ASSIGN(LockWindow);
91 }; 101 };
92 102
93 } // namespace 103 } // namespace
94 104
95 namespace chromeos { 105 namespace chromeos {
96 106
97 //////////////////////////////////////////////////////////////////////////////// 107 ////////////////////////////////////////////////////////////////////////////////
98 // WebUIScreenLocker implementation. 108 // WebUIScreenLocker implementation.
99 109
100 WebUIScreenLocker::WebUIScreenLocker(ScreenLocker* screen_locker) 110 WebUIScreenLocker::WebUIScreenLocker(ScreenLocker* screen_locker)
101 : ScreenLockerDelegate(screen_locker) { 111 : ScreenLockerDelegate(screen_locker),
112 drawn_(false),
113 input_grabbed_(false),
114 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
115 grab_failure_count_(0),
116 kbd_grab_status_(GDK_GRAB_INVALID_TIME),
117 mouse_grab_status_(GDK_GRAB_INVALID_TIME) {
102 } 118 }
103 119
104 void WebUIScreenLocker::LockScreen(bool unlock_on_input) { 120 void WebUIScreenLocker::LockScreen(bool unlock_on_input) {
105 static const GdkColor kGdkBlack = {0, 0, 0, 0}; 121 static const GdkColor kGdkBlack = {0, 0, 0, 0};
106 122
107 gfx::Rect bounds(gfx::Screen::GetMonitorAreaNearestWindow(NULL)); 123 gfx::Rect bounds(gfx::Screen::GetMonitorAreaNearestWindow(NULL));
108 124
109 LockWindow* lock_window = new LockWindow(); 125 LockWindow* lock_window = new LockWindow(this);
110 lock_window_ = lock_window->GetWidget(); 126 lock_window_ = lock_window->GetWidget();
111 views::Widget::InitParams params( 127 views::Widget::InitParams params(
112 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); 128 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
113 params.bounds = bounds; 129 params.bounds = bounds;
114 params.native_widget = lock_window; 130 params.native_widget = lock_window;
115 lock_window_->Init(params); 131 lock_window_->Init(params);
116 gtk_widget_modify_bg( 132 gtk_widget_modify_bg(
117 lock_window_->GetNativeView(), GTK_STATE_NORMAL, &kGdkBlack); 133 lock_window_->GetNativeView(), GTK_STATE_NORMAL, &kGdkBlack);
118 134
119 g_signal_connect(lock_window_->GetNativeView(), "client-event", 135 g_signal_connect(lock_window_->GetNativeView(), "client-event",
(...skipping 19 matching lines...) Expand all
139 login_display_->set_parent_window( 155 login_display_->set_parent_window(
140 GTK_WINDOW(lock_window_->GetNativeView())); 156 GTK_WINDOW(lock_window_->GetNativeView()));
141 login_display_->Init(users, false, false); 157 login_display_->Init(users, false, false);
142 158
143 static_cast<OobeUI*>(GetWebUI())->ShowSigninScreen(login_display_.get()); 159 static_cast<OobeUI*>(GetWebUI())->ShowSigninScreen(login_display_.get());
144 160
145 registrar_.Add(this, 161 registrar_.Add(this,
146 chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED, 162 chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED,
147 content::NotificationService::AllSources()); 163 content::NotificationService::AllSources());
148 164
165 ClearGtkGrab();
166
167 // Call this after lock_window_->Show(); otherwise the 1st invocation
168 // of gdk_xxx_grab() will always fail.
169 TryGrabAllInputs();
170
149 // Add the window to its own group so that its grab won't be stolen if 171 // Add the window to its own group so that its grab won't be stolen if
150 // gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g. 172 // gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g.
151 // a modal dialog) -- see http://crosbug.com/8999. We intentionally do this 173 // a modal dialog) -- see http://crosbug.com/8999. We intentionally do this
152 // after calling ClearGtkGrab(), as want to be in the default window group 174 // after calling ClearGtkGrab(), as want to be in the default window group
153 // then so we can break any existing GTK grabs. 175 // then so we can break any existing GTK grabs.
154 GtkWindowGroup* window_group = gtk_window_group_new(); 176 GtkWindowGroup* window_group = gtk_window_group_new();
155 gtk_window_group_add_window(window_group, 177 gtk_window_group_add_window(window_group,
156 GTK_WINDOW(lock_window_->GetNativeView())); 178 GTK_WINDOW(lock_window_->GetNativeView()));
157 g_object_unref(window_group); 179 g_object_unref(window_group);
158 180
159 lock_window->set_toplevel_focus_widget(lock_window->window_contents()); 181 lock_window->set_toplevel_focus_widget(lock_window->window_contents());
160 } 182 }
161 183
184 void WebUIScreenLocker::OnGrabInputs() {
185 DVLOG(1) << "OnGrabInputs";
186 input_grabbed_ = true;
187 if (drawn_)
188 ScreenLockReady();
189 }
190
191 void WebUIScreenLocker::OnWindowManagerReady() {
192 DVLOG(1) << "OnClientEvent: drawn for lock";
193 drawn_ = true;
194 if (input_grabbed_)
195 ScreenLockReady();
196 }
197
162 void WebUIScreenLocker::ScreenLockReady() { 198 void WebUIScreenLocker::ScreenLockReady() {
163 ScreenLockerDelegate::ScreenLockReady(); 199 ScreenLockerDelegate::ScreenLockReady();
164 SetInputEnabled(true); 200 SetInputEnabled(true);
165 } 201 }
166 202
203 void WebUIScreenLocker::ClearGtkGrab() {
204 GtkWidget* current_grab_window;
205 // Grab gtk input first so that the menu holding gtk grab will
206 // close itself.
207 gtk_grab_add(webui_login_->native_view());
208
209 // Make sure there is no gtk grab widget so that gtk simply propagates
210 // an event. GTK maintains grab widgets in a linked-list, so we need to
211 // remove until it's empty.
212 while ((current_grab_window = gtk_grab_get_current()) != NULL)
213 gtk_grab_remove(current_grab_window);
214 }
215
216 void WebUIScreenLocker::TryGrabAllInputs() {
217 // Grab on the RenderWidgetHostView hosting the WebUI login screen.
218 GdkWindow* grab_widget = webui_login_->dom_contents()->tab_contents()->
219 GetRenderWidgetHostView()->GetNativeView()->window;
220 // Grab x server so that we can atomically grab and take
221 // action when grab fails.
222 gdk_x11_grab_server();
223 gtk_grab_add(webui_login_->native_view());
224 if (kbd_grab_status_ != GDK_GRAB_SUCCESS)
225 kbd_grab_status_ = gdk_keyboard_grab(grab_widget, FALSE, GDK_CURRENT_TIME);
226 if (mouse_grab_status_ != GDK_GRAB_SUCCESS) {
227 mouse_grab_status_ =
228 gdk_pointer_grab(grab_widget,
229 FALSE,
230 static_cast<GdkEventMask>(
231 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
232 GDK_POINTER_MOTION_MASK),
233 NULL,
234 NULL,
235 GDK_CURRENT_TIME);
236 }
237 if ((kbd_grab_status_ != GDK_GRAB_SUCCESS ||
238 mouse_grab_status_ != GDK_GRAB_SUCCESS) &&
239 grab_failure_count_++ < kMaxGrabFailures) {
240 LOG(WARNING) << "Failed to grab inputs. Trying again in "
241 << kRetryGrabIntervalMs << " ms: kbd="
242 << kbd_grab_status_ << ", mouse=" << mouse_grab_status_;
243 TryUngrabOtherClients();
244 gdk_x11_ungrab_server();
245 MessageLoop::current()->PostDelayedTask(
246 FROM_HERE,
247 base::Bind(&WebUIScreenLocker::TryGrabAllInputs,
248 weak_factory_.GetWeakPtr()),
249 kRetryGrabIntervalMs);
250 } else {
251 gdk_x11_ungrab_server();
252 GdkGrabStatus status = kbd_grab_status_;
253 if (status == GDK_GRAB_SUCCESS) {
254 status = mouse_grab_status_;
255 }
256 switch (status) {
257 case GDK_GRAB_SUCCESS:
258 break;
259 case GDK_GRAB_ALREADY_GRABBED:
260 FailedWithGrabAlreadyGrabbed();
261 break;
262 case GDK_GRAB_INVALID_TIME:
263 FailedWithGrabInvalidTime();
264 break;
265 case GDK_GRAB_NOT_VIEWABLE:
266 FailedWithGrabNotViewable();
267 break;
268 case GDK_GRAB_FROZEN:
269 FailedWithGrabFrozen();
270 break;
271 default:
272 FailedWithUnknownError();
273 break;
274 }
275 DVLOG(1) << "Grab Success";
276 OnGrabInputs();
277 }
278 }
279
280 void WebUIScreenLocker::TryUngrabOtherClients() {
281 #if !defined(NDEBUG)
282 {
283 int event_base, error_base;
284 int major, minor;
285 // Make sure we have XTest extension.
286 DCHECK(XTestQueryExtension(ui::GetXDisplay(),
287 &event_base, &error_base,
288 &major, &minor));
289 }
290 #endif
291
292 // The following code is an attempt to grab inputs by closing
293 // supposedly opened menu. This happens when a plugin has a menu
294 // opened.
295 if (mouse_grab_status_ == GDK_GRAB_ALREADY_GRABBED ||
296 mouse_grab_status_ == GDK_GRAB_FROZEN) {
297 // Successfully grabbed the keyboard, but pointer is still
298 // grabbed by other client. Another attempt to close supposedly
299 // opened menu by emulating keypress at the left top corner.
300 Display* display = ui::GetXDisplay();
301 Window root, child;
302 int root_x, root_y, win_x, winy;
303 unsigned int mask;
304 XQueryPointer(display,
305 ui::GetX11WindowFromGtkWidget(
306 static_cast<LockWindow*>(lock_window_->native_widget())->
307 window_contents()),
308 &root, &child, &root_x, &root_y,
309 &win_x, &winy, &mask);
310 XTestFakeMotionEvent(display, -1, -10000, -10000, CurrentTime);
311 XTestFakeButtonEvent(display, 1, True, CurrentTime);
312 XTestFakeButtonEvent(display, 1, False, CurrentTime);
313 // Move the pointer back.
314 XTestFakeMotionEvent(display, -1, root_x, root_y, CurrentTime);
315 XFlush(display);
316 } else if (kbd_grab_status_ == GDK_GRAB_ALREADY_GRABBED ||
317 kbd_grab_status_ == GDK_GRAB_FROZEN) {
318 // Successfully grabbed the pointer, but keyboard is still grabbed
319 // by other client. Another attempt to close supposedly opened
320 // menu by emulating escape key. Such situation must be very
321 // rare, but handling this just in case
322 Display* display = ui::GetXDisplay();
323 KeyCode escape = XKeysymToKeycode(display, XK_Escape);
324 XTestFakeKeyEvent(display, escape, True, CurrentTime);
325 XTestFakeKeyEvent(display, escape, False, CurrentTime);
326 XFlush(display);
327 }
328 }
329
330 void WebUIScreenLocker::HandleGtkGrabBroke() {
331 // Input should never be stolen from ScreenLocker once it's
332 // grabbed. If this happens, it's a bug and has to be fixed. We
333 // let chrome crash to get a crash report and dump, and
334 // SessionManager will terminate the session to logout.
335 CHECK_NE(GDK_GRAB_SUCCESS, kbd_grab_status_);
336 CHECK_NE(GDK_GRAB_SUCCESS, mouse_grab_status_);
337 }
338
339 void WebUIScreenLocker::FailedWithGrabAlreadyGrabbed() {
340 LOG(FATAL) << "Grab already grabbed";
341 }
342 void WebUIScreenLocker::FailedWithGrabInvalidTime() {
343 LOG(FATAL) << "Grab invalid time";
344 }
345 void WebUIScreenLocker::FailedWithGrabNotViewable() {
346 LOG(FATAL) << "Grab not viewable";
347 }
348 void WebUIScreenLocker::FailedWithGrabFrozen() {
349 LOG(FATAL) << "Grab frozen";
350 }
351 void WebUIScreenLocker::FailedWithUnknownError() {
352 LOG(FATAL) << "Grab uknown";
353 }
354
167 void WebUIScreenLocker::OnAuthenticate() { 355 void WebUIScreenLocker::OnAuthenticate() {
168 } 356 }
169 357
170 void WebUIScreenLocker::SetInputEnabled(bool enabled) { 358 void WebUIScreenLocker::SetInputEnabled(bool enabled) {
171 login_display_->SetUIEnabled(enabled); 359 login_display_->SetUIEnabled(enabled);
172 SetStatusAreaEnabled(enabled); 360 SetStatusAreaEnabled(enabled);
173 } 361 }
174 362
175 void WebUIScreenLocker::SetSignoutEnabled(bool enabled) { 363 void WebUIScreenLocker::SetSignoutEnabled(bool enabled) {
176 // TODO(flackr): Implement. 364 // TODO(flackr): Implement.
(...skipping 26 matching lines...) Expand all
203 391
204 WebUIScreenLocker::~WebUIScreenLocker() { 392 WebUIScreenLocker::~WebUIScreenLocker() {
205 DCHECK(lock_window_); 393 DCHECK(lock_window_);
206 lock_window_->Close(); 394 lock_window_->Close();
207 } 395 }
208 396
209 void WebUIScreenLocker::OnClientEvent(GtkWidget* widge, GdkEventClient* event) { 397 void WebUIScreenLocker::OnClientEvent(GtkWidget* widge, GdkEventClient* event) {
210 WmIpc::Message msg; 398 WmIpc::Message msg;
211 WmIpc::instance()->DecodeMessage(*event, &msg); 399 WmIpc::instance()->DecodeMessage(*event, &msg);
212 if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK) 400 if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK)
213 ScreenLockReady(); 401 OnWindowManagerReady();
214 } 402 }
215 403
216 //////////////////////////////////////////////////////////////////////////////// 404 ////////////////////////////////////////////////////////////////////////////////
217 // WebUIScreenLocker, content::NotificationObserver implementation: 405 // WebUIScreenLocker, content::NotificationObserver implementation:
218 406
219 void WebUIScreenLocker::Observe( 407 void WebUIScreenLocker::Observe(
220 int type, 408 int type,
221 const content::NotificationSource& source, 409 const content::NotificationSource& source,
222 const content::NotificationDetails& details) { 410 const content::NotificationDetails& details) {
223 if (type != chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED) 411 if (type != chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED)
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 } 448 }
261 449
262 void WebUIScreenLocker::OnUserSelected(const std::string& username) { 450 void WebUIScreenLocker::OnUserSelected(const std::string& username) {
263 } 451 }
264 452
265 void WebUIScreenLocker::OnStartEnterpriseEnrollment() { 453 void WebUIScreenLocker::OnStartEnterpriseEnrollment() {
266 NOTREACHED(); 454 NOTREACHED();
267 } 455 }
268 456
269 } // namespace chromeos 457 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698