Chromium Code Reviews| Index: chrome/browser/chromeos/login/webui_screen_locker.cc |
| diff --git a/chrome/browser/chromeos/login/webui_screen_locker.cc b/chrome/browser/chromeos/login/webui_screen_locker.cc |
| index afd1665f60cec0b3e508a0db05243af9965db79b..e78499c5abc9539485cd1a57454f7471d2f9c062 100644 |
| --- a/chrome/browser/chromeos/login/webui_screen_locker.cc |
| +++ b/chrome/browser/chromeos/login/webui_screen_locker.cc |
| @@ -23,9 +23,11 @@ |
| #include "chrome/browser/chromeos/login/user_manager.h" |
| #include "chrome/browser/chromeos/login/webui_login_display.h" |
| #include "chrome/browser/chromeos/wm_ipc.h" |
| +#include "chrome/browser/ui/views/dom_view.h" |
| #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h" |
| #include "chrome/common/chrome_notification_types.h" |
| #include "chrome/common/url_constants.h" |
| +#include "content/browser/renderer_host/render_widget_host_view.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "ui/base/l10n/l10n_util.h" |
| @@ -38,27 +40,33 @@ namespace { |
| // URL which corresponds to the login WebUI. |
| const char kLoginURL[] = "chrome://oobe/login"; |
| +// The maximum duration for which locker should try to grab the keyboard and |
| +// mouse and its interval for regrabbing on failure. |
| +const int kMaxGrabFailureSec = 30; |
| +const int64 kRetryGrabIntervalMs = 500; |
| + |
| +// Maximum number of times we'll try to grab the keyboard and mouse before |
| +// giving up. If we hit the limit, Chrome exits and the session is terminated. |
| +const int kMaxGrabFailures = kMaxGrabFailureSec * 1000 / kRetryGrabIntervalMs; |
| + |
| // A ScreenLock window that covers entire screen to keep the keyboard |
| // focus/events inside the grab widget. |
| class LockWindow : public views::NativeWidgetGtk { |
| public: |
| - LockWindow() |
| + explicit LockWindow(chromeos::WebUIScreenLocker* webui_screen_locker) |
| : views::NativeWidgetGtk(new views::Widget), |
| - toplevel_focus_widget_(NULL) { |
| + webui_screen_locker_(webui_screen_locker), |
| + toplevel_focus_widget_(NULL), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), |
| + grab_failure_count_(0), |
| + kbd_grab_status_(GDK_GRAB_INVALID_TIME), |
| + mouse_grab_status_(GDK_GRAB_INVALID_TIME) { |
| EnableDoubleBuffer(true); |
| } |
| - // GTK propagates key events from parents to children. |
| - // Make sure LockWindow will never handle key events. |
| - virtual gboolean OnEventKey(GtkWidget* widget, GdkEventKey* event) OVERRIDE { |
| - // Don't handle key event in the lock window. |
| - return false; |
| - } |
| - |
| virtual gboolean OnButtonPress(GtkWidget* widget, |
| GdkEventButton* event) OVERRIDE { |
| - // Don't handle mouse event in the lock wnidow and |
| - // nor propagate to child. |
| + // Never propagate mouse events to parent. |
| return true; |
| } |
| @@ -79,17 +87,189 @@ class LockWindow : public views::NativeWidgetGtk { |
| toplevel_focus_widget_ = widget; |
| } |
| + void ClearGtkGrab() { |
| + GtkWidget* current_grab_window; |
| + // Grab gtk input first so that the menu holding gtk grab will |
| + // close itself. |
| + gtk_grab_add(window_contents()); |
| + |
| + // Make sure there is no gtk grab widget so that gtk simply propagates |
| + // an event. This is necessary to allow message bubble and password |
| + // field, button to process events simultaneously. GTK |
| + // maintains grab widgets in a linked-list, so we need to remove |
| + // until it's empty. |
| + while ((current_grab_window = gtk_grab_get_current()) != NULL) |
| + gtk_grab_remove(current_grab_window); |
| + } |
| + |
| + // Try to grab all inputs. It initiates another try if it fails to |
| + // grab and the retry count is within a limit, or fails with CHECK. |
| + void TryGrabAllInputs(); |
| + |
| + // This method tries to steal pointer/keyboard grab from other |
| + // client by sending events that will hopefully close menus or windows |
| + // that have the grab. |
| + void TryUngrabOtherClients(); |
| + |
| private: |
| + virtual void HandleGtkGrabBroke() OVERRIDE { |
| + // Input should never be stolen from ScreenLocker once it's |
| + // grabbed. If this happens, it's a bug and has to be fixed. We |
| + // let chrome crash to get a crash report and dump, and |
| + // SessionManager will terminate the session to logout. |
| + CHECK_NE(GDK_GRAB_SUCCESS, kbd_grab_status_); |
| + CHECK_NE(GDK_GRAB_SUCCESS, mouse_grab_status_); |
| + } |
| + |
| + // Define separate methods for each error code so that stack trace |
| + // will tell which error the grab failed with. |
| + void FailedWithGrabAlreadyGrabbed() { |
| + LOG(FATAL) << "Grab already grabbed"; |
| + } |
| + void FailedWithGrabInvalidTime() { |
| + LOG(FATAL) << "Grab invalid time"; |
| + } |
| + void FailedWithGrabNotViewable() { |
| + LOG(FATAL) << "Grab not viewable"; |
| + } |
| + void FailedWithGrabFrozen() { |
| + LOG(FATAL) << "Grab frozen"; |
| + } |
| + void FailedWithUnknownError() { |
| + LOG(FATAL) << "Grab uknown"; |
| + } |
| + |
| + chromeos::WebUIScreenLocker* webui_screen_locker_; |
| + |
| // The widget we set focus to when clearning the focus on native |
| - // widget. In screen locker, gdk input is grabbed in GrabWidget, |
| - // and resetting the focus by using gtk_window_set_focus seems to |
| - // confuse gtk and doesn't let focus move to native widget under |
| - // GrabWidget. |
| + // widget. Gdk input is grabbed in this class, and resetting the focus |
| + // by using gtk_window_set_focus seems to confuse gtk and doesn't let focus |
| + // move to native widget under this. |
| GtkWidget* toplevel_focus_widget_; |
| + base::WeakPtrFactory<LockWindow> weak_factory_; |
| + |
| + // The number times the widget tried to grab all focus. |
| + int grab_failure_count_; |
|
oshima
2011/11/18 17:22:06
new line after this
flackr
2011/11/18 21:49:09
Done.
|
| + // Status of keyboard and mouse grab. |
| + GdkGrabStatus kbd_grab_status_; |
| + GdkGrabStatus mouse_grab_status_; |
| + |
| DISALLOW_COPY_AND_ASSIGN(LockWindow); |
| }; |
| +void LockWindow::TryGrabAllInputs() { |
| + // Grab on the RenderWidgetHostView hosting the WebUI login screen. |
| + GdkWindow* grabWidget = webui_screen_locker_->dom_view()->dom_contents()-> |
|
sadrul
2011/11/18 17:36:17
ew! grab_widget
flackr
2011/11/18 21:49:09
Done. Forgive me!
|
| + tab_contents()->GetRenderWidgetHostView()->GetNativeView()->window; |
|
oshima
2011/11/18 17:22:06
Am I correct that grabbing input doesn't require s
flackr
2011/11/18 21:49:09
Done.
|
| + // Grab x server so that we can atomically grab and take |
| + // action when grab fails. |
| + gdk_x11_grab_server(); |
| + gtk_grab_add(webui_screen_locker_->dom_view()->native_view()); |
|
oshima
2011/11/18 17:22:06
Have you tried using this dom_view()->native_view(
flackr
2011/11/18 21:49:09
I have, this didn't work.
|
| + if (kbd_grab_status_ != GDK_GRAB_SUCCESS) { |
| + kbd_grab_status_ = gdk_keyboard_grab(grabWidget, FALSE, GDK_CURRENT_TIME); |
| + } |
|
oshima
2011/11/18 17:22:06
nuke {}
flackr
2011/11/18 21:49:09
Done. The following if is a single statement, but
oshima
2011/11/18 22:44:55
correct
|
| + if (mouse_grab_status_ != GDK_GRAB_SUCCESS) { |
| + mouse_grab_status_ = |
| + gdk_pointer_grab(grabWidget, |
| + FALSE, |
| + static_cast<GdkEventMask>( |
| + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | |
| + GDK_POINTER_MOTION_MASK), |
| + NULL, |
| + NULL, |
| + GDK_CURRENT_TIME); |
| + } |
| + if ((kbd_grab_status_ != GDK_GRAB_SUCCESS || |
| + mouse_grab_status_ != GDK_GRAB_SUCCESS) && |
| + grab_failure_count_++ < kMaxGrabFailures) { |
| + LOG(WARNING) << "Failed to grab inputs. Trying again in " |
| + << kRetryGrabIntervalMs << " ms: kbd=" |
| + << kbd_grab_status_ << ", mouse=" << mouse_grab_status_; |
| + TryUngrabOtherClients(); |
| + gdk_x11_ungrab_server(); |
| + MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&LockWindow::TryGrabAllInputs, weak_factory_.GetWeakPtr()), |
| + kRetryGrabIntervalMs); |
| + } else { |
| + gdk_x11_ungrab_server(); |
| + GdkGrabStatus status = kbd_grab_status_; |
| + if (status == GDK_GRAB_SUCCESS) { |
| + status = mouse_grab_status_; |
| + } |
| + switch (status) { |
| + case GDK_GRAB_SUCCESS: |
| + break; |
| + case GDK_GRAB_ALREADY_GRABBED: |
| + FailedWithGrabAlreadyGrabbed(); |
| + break; |
| + case GDK_GRAB_INVALID_TIME: |
| + FailedWithGrabInvalidTime(); |
| + break; |
| + case GDK_GRAB_NOT_VIEWABLE: |
| + FailedWithGrabNotViewable(); |
| + break; |
| + case GDK_GRAB_FROZEN: |
| + FailedWithGrabFrozen(); |
| + break; |
| + default: |
| + FailedWithUnknownError(); |
| + break; |
| + } |
| + DVLOG(1) << "Grab Success"; |
| + webui_screen_locker_->OnGrabInputs(); |
| + } |
| +} |
| + |
| +void LockWindow::TryUngrabOtherClients() { |
| +#if !defined(NDEBUG) |
| + { |
| + int event_base, error_base; |
| + int major, minor; |
| + // Make sure we have XTest extension. |
| + DCHECK(XTestQueryExtension(ui::GetXDisplay(), |
| + &event_base, &error_base, |
| + &major, &minor)); |
| + } |
| +#endif |
| + |
| + // The following code is an attempt to grab inputs by closing |
| + // supposedly opened menu. This happens when a plugin has a menu |
| + // opened. |
| + if (mouse_grab_status_ == GDK_GRAB_ALREADY_GRABBED || |
| + mouse_grab_status_ == GDK_GRAB_FROZEN) { |
| + // Successfully grabbed the keyboard, but pointer is still |
| + // grabbed by other client. Another attempt to close supposedly |
| + // opened menu by emulating keypress at the left top corner. |
| + Display* display = ui::GetXDisplay(); |
| + Window root, child; |
| + int root_x, root_y, win_x, winy; |
| + unsigned int mask; |
| + XQueryPointer(display, |
| + ui::GetX11WindowFromGtkWidget(window_contents()), |
| + &root, &child, &root_x, &root_y, |
| + &win_x, &winy, &mask); |
| + XTestFakeMotionEvent(display, -1, -10000, -10000, CurrentTime); |
| + XTestFakeButtonEvent(display, 1, True, CurrentTime); |
| + XTestFakeButtonEvent(display, 1, False, CurrentTime); |
| + // Move the pointer back. |
| + XTestFakeMotionEvent(display, -1, root_x, root_y, CurrentTime); |
| + XFlush(display); |
| + } else if (kbd_grab_status_ == GDK_GRAB_ALREADY_GRABBED || |
| + kbd_grab_status_ == GDK_GRAB_FROZEN) { |
| + // Successfully grabbed the pointer, but keyboard is still grabbed |
| + // by other client. Another attempt to close supposedly opened |
| + // menu by emulating escape key. Such situation must be very |
| + // rare, but handling this just in case |
| + Display* display = ui::GetXDisplay(); |
| + KeyCode escape = XKeysymToKeycode(display, XK_Escape); |
| + XTestFakeKeyEvent(display, escape, True, CurrentTime); |
| + XTestFakeKeyEvent(display, escape, False, CurrentTime); |
| + XFlush(display); |
| + } |
| +} |
| + |
| } // namespace |
| namespace chromeos { |
| @@ -98,7 +278,9 @@ namespace chromeos { |
| // WebUIScreenLocker implementation. |
| WebUIScreenLocker::WebUIScreenLocker(ScreenLocker* screen_locker) |
| - : ScreenLockerDelegate(screen_locker) { |
| + : ScreenLockerDelegate(screen_locker), |
| + drawn_(false), |
| + input_grabbed_(false) { |
| } |
| void WebUIScreenLocker::LockScreen(bool unlock_on_input) { |
| @@ -106,7 +288,7 @@ void WebUIScreenLocker::LockScreen(bool unlock_on_input) { |
| gfx::Rect bounds(gfx::Screen::GetMonitorAreaNearestWindow(NULL)); |
| - LockWindow* lock_window = new LockWindow(); |
| + LockWindow* lock_window = new LockWindow(this); |
| lock_window_ = lock_window->GetWidget(); |
| views::Widget::InitParams params( |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| @@ -146,6 +328,12 @@ void WebUIScreenLocker::LockScreen(bool unlock_on_input) { |
| chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED, |
| content::NotificationService::AllSources()); |
| + lock_window->ClearGtkGrab(); |
| + |
| + // Call this after lock_window_->Show(); otherwise the 1st invocation |
| + // of gdk_xxx_grab() will always fail. |
| + lock_window->TryGrabAllInputs(); |
| + |
| // Add the window to its own group so that its grab won't be stolen if |
| // gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g. |
| // a modal dialog) -- see http://crosbug.com/8999. We intentionally do this |
| @@ -159,6 +347,20 @@ void WebUIScreenLocker::LockScreen(bool unlock_on_input) { |
| lock_window->set_toplevel_focus_widget(lock_window->window_contents()); |
| } |
| +void WebUIScreenLocker::OnGrabInputs() { |
| + DVLOG(1) << "OnGrabInputs"; |
| + input_grabbed_ = true; |
| + if (drawn_) |
| + ScreenLockReady(); |
| +} |
| + |
| +void WebUIScreenLocker::OnWindowManagerReady() { |
| + DVLOG(1) << "OnClientEvent: drawn for lock"; |
| + drawn_ = true; |
| + if (input_grabbed_) |
| + ScreenLockReady(); |
| +} |
| + |
| void WebUIScreenLocker::ScreenLockReady() { |
| ScreenLockerDelegate::ScreenLockReady(); |
| SetInputEnabled(true); |
| @@ -210,7 +412,7 @@ void WebUIScreenLocker::OnClientEvent(GtkWidget* widge, GdkEventClient* event) { |
| WmIpc::Message msg; |
| WmIpc::instance()->DecodeMessage(*event, &msg); |
| if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK) |
| - ScreenLockReady(); |
| + OnWindowManagerReady(); |
| } |
| //////////////////////////////////////////////////////////////////////////////// |