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

Unified 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 failure functions to anonymous namespace. 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/chromeos/login/webui_screen_locker.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 6b64dc5afe73556e9648540b0a87d4a39fcb1e61..8a253499208a4369e8eda6e7114d795683904ab8 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/screen_locker.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/login/webui_login_display.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,29 @@ 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) {
+ toplevel_focus_widget_(NULL),
+ webui_screen_locker_(webui_screen_locker) {
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;
}
@@ -72,6 +76,10 @@ class LockWindow : public views::NativeWidgetGtk {
gtk_widget_grab_focus(toplevel_focus_widget_);
}
+ virtual void HandleGtkGrabBroke() OVERRIDE {
+ webui_screen_locker_->HandleGtkGrabBroke();
+ }
+
// Sets the widget to move the focus to when clearning the native
// widget's focus.
void set_toplevel_focus_widget(GtkWidget* widget) {
@@ -81,15 +89,35 @@ class LockWindow : public views::NativeWidgetGtk {
private:
// 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 WebUIScreenLocker, 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_;
+ // The WebUI screen locker.
+ chromeos::WebUIScreenLocker* webui_screen_locker_;
+
DISALLOW_COPY_AND_ASSIGN(LockWindow);
};
+// 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";
+}
+
} // namespace
namespace chromeos {
@@ -98,7 +126,13 @@ namespace chromeos {
// WebUIScreenLocker implementation.
WebUIScreenLocker::WebUIScreenLocker(ScreenLocker* screen_locker)
- : ScreenLockerDelegate(screen_locker) {
+ : ScreenLockerDelegate(screen_locker),
+ drawn_(false),
+ input_grabbed_(false),
+ 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) {
}
void WebUIScreenLocker::LockScreen(bool unlock_on_input) {
@@ -106,7 +140,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 +180,12 @@ void WebUIScreenLocker::LockScreen(bool unlock_on_input) {
chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED,
content::NotificationService::AllSources());
+ ClearGtkGrab();
+
+ // Call this after lock_window_->Show(); otherwise the 1st invocation
+ // of gdk_xxx_grab() will always fail.
+ 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,11 +199,161 @@ 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);
}
+void WebUIScreenLocker::ClearGtkGrab() {
+ GtkWidget* current_grab_window;
+ // Grab gtk input first so that the menu holding gtk grab will
+ // close itself.
+ gtk_grab_add(webui_login_->native_view());
+
+ // Make sure there is no gtk grab widget so that gtk simply propagates
+ // an event. 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);
+}
+
+void WebUIScreenLocker::TryGrabAllInputs() {
+ // Grab on the RenderWidgetHostView hosting the WebUI login screen.
+ GdkWindow* grab_widget = webui_login_->dom_contents()->tab_contents()->
+ GetRenderWidgetHostView()->GetNativeView()->window;
+ // Grab x server so that we can atomically grab and take
+ // action when grab fails.
+ gdk_x11_grab_server();
+ gtk_grab_add(webui_login_->native_view());
+ if (kbd_grab_status_ != GDK_GRAB_SUCCESS)
+ kbd_grab_status_ = gdk_keyboard_grab(grab_widget, FALSE, GDK_CURRENT_TIME);
+ if (mouse_grab_status_ != GDK_GRAB_SUCCESS) {
+ mouse_grab_status_ =
+ gdk_pointer_grab(grab_widget,
+ 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(&WebUIScreenLocker::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";
+ OnGrabInputs();
+ }
+}
+
+void WebUIScreenLocker::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(
+ static_cast<LockWindow*>(lock_window_->native_widget())->
+ 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);
+ }
+}
+
+void WebUIScreenLocker::HandleGtkGrabBroke() {
+ // 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_);
+}
+
void WebUIScreenLocker::OnAuthenticate() {
}
@@ -210,7 +400,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();
}
////////////////////////////////////////////////////////////////////////////////
« no previous file with comments | « chrome/browser/chromeos/login/webui_screen_locker.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698