blob: d4d5f8e28774ff424e46a0fc84a73f72aaacea1d [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "chromeos/dbus/service_constants.h"
#include "cros/chromeos_wm_ipc_enums.h"
#include "window_manager/compositor/compositor.h"
#include "window_manager/event_loop.h"
#include "window_manager/login/login_controller.h"
#include "window_manager/shadow.h"
#include "window_manager/stacking_manager.h"
#include "window_manager/test_lib.h"
#include "window_manager/util.h"
#include "window_manager/window.h"
#include "window_manager/window_manager.h"
#include "window_manager/wm_ipc.h"
#include "window_manager/x11/mock_x_connection.h"
DEFINE_bool(logtostderr, false,
"Print debugging messages to stderr (suppressed otherwise)");
using std::vector;
namespace window_manager {
class LoginControllerTest : public BasicWindowManagerTest {
protected:
static const int kUnselectedImageSize;
static const int kGapBetweenImageAndControls;
static const int kImageSize;
static const int kControlsSize;
// A collection of windows for a single login entry.
struct EntryWindows {
EntryWindows()
: border_xid(0),
image_xid(0),
controls_xid(0),
label_xid(0),
unselected_label_xid(0) {
}
XWindow border_xid;
XWindow image_xid;
XWindow controls_xid;
XWindow label_xid;
XWindow unselected_label_xid;
};
// A collection of bounds of login entry's composited windows.
struct EntryBounds {
Rect border;
Rect image;
Rect controls;
Rect label;
Rect unselected_label;
};
virtual void SetUp() {
BasicWindowManagerTest::SetUp();
wm_.reset(NULL);
// Use a WindowManager object that thinks that Chrome isn't logged in
// yet so that LoginController will manage non-login windows as well.
SetLoggedInState(false);
CreateAndInitNewWm();
login_controller_ = wm_->login_controller_.get();
background_xid_ = 0;
wizard_xid_ = 0;
webui_window_xid_ = 0;
}
void CreateWebUILoginWindow() {
if (!webui_window_xid_) {
webui_window_xid_ = CreateBasicWindow(wm_->root_bounds());
}
wm_->wm_ipc()->SetWindowType(webui_window_xid_,
chromeos::WM_IPC_WINDOW_LOGIN_WEBUI,
NULL);
SendInitialEventsForWindow(webui_window_xid_);
}
// Create the set of windows expected by LoginController.
void CreateLoginWindows(int num_entries,
bool background_is_ready,
bool entry_pixmaps_are_ready,
bool create_wizard_window) {
CHECK(num_entries == 0 || num_entries >= 2);
if (!background_xid_) {
background_xid_ = CreateBasicWindow(wm_->root_bounds());
vector<int> background_params;
background_params.push_back(background_is_ready ? 1 : 0);
wm_->wm_ipc()->SetWindowType(background_xid_,
chromeos::WM_IPC_WINDOW_LOGIN_BACKGROUND,
&background_params);
SendInitialEventsForWindow(background_xid_);
}
for (int i = 0; i < num_entries; ++i) {
entries_.push_back(CreateLoginEntry(num_entries, i));
}
// The wizard window needs to be mapped after the entries. Otherwise, when
// LoginController sees the wizard window get mapped, it won't know whether
// it should display it immediately or wait for entries to show up.
if (create_wizard_window) {
wizard_xid_ =
CreateBasicWindow(Rect(0, 0, wm_->width() / 2, wm_->height() / 2));
wm_->wm_ipc()->SetWindowType(wizard_xid_,
chromeos::WM_IPC_WINDOW_LOGIN_GUEST,
NULL);
SendInitialEventsForWindow(wizard_xid_);
}
if (entry_pixmaps_are_ready) {
for (int i = 0; i < num_entries; ++i)
SendInitialPixmapEventForEntry(i);
// LoginController registers a timeout to call this, so we need to call
// it manually.
// TODO: It'd be better to make it so that tests can manually run
// timeouts that have been posted to EventLoop.
if (num_entries > 0)
login_controller_->InitialShow();
}
}
EntryWindows CreateLoginEntry(int num_entries, int i) {
EntryWindows entry;
entry.border_xid =
CreateBasicWindow(
Rect(0, 0,
kImageSize + 2 * kGapBetweenImageAndControls,
kImageSize + kControlsSize + 3 * kGapBetweenImageAndControls));
entry.image_xid =
CreateBasicWindow(Rect(0, 0, kImageSize, kImageSize));
entry.controls_xid =
CreateBasicWindow(Rect(0, 0, kImageSize, kControlsSize));
entry.label_xid =
CreateBasicWindow(Rect(0, 0, kImageSize, kControlsSize));
entry.unselected_label_xid =
CreateBasicWindow(Rect(0, 0, kImageSize, kControlsSize));
vector<int> params;
params.push_back(i); // entry index
wm_->wm_ipc()->SetWindowType(
entry.image_xid,
chromeos::WM_IPC_WINDOW_LOGIN_IMAGE,
&params);
wm_->wm_ipc()->SetWindowType(
entry.controls_xid,
chromeos::WM_IPC_WINDOW_LOGIN_CONTROLS,
&params);
wm_->wm_ipc()->SetWindowType(
entry.label_xid,
chromeos::WM_IPC_WINDOW_LOGIN_LABEL,
&params);
wm_->wm_ipc()->SetWindowType(
entry.unselected_label_xid,
chromeos::WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL,
&params);
// The border window stores some additional parameters.
params.push_back(num_entries);
params.push_back(kUnselectedImageSize);
params.push_back(kGapBetweenImageAndControls);
wm_->wm_ipc()->SetWindowType(
entry.border_xid,
chromeos::WM_IPC_WINDOW_LOGIN_BORDER,
&params);
ConfigureWindowForSyncRequestProtocol(entry.border_xid);
ConfigureWindowForSyncRequestProtocol(entry.image_xid);
ConfigureWindowForSyncRequestProtocol(entry.controls_xid);
ConfigureWindowForSyncRequestProtocol(entry.label_xid);
ConfigureWindowForSyncRequestProtocol(entry.unselected_label_xid);
SendInitialEventsForWindow(entry.border_xid);
SendInitialEventsForWindow(entry.image_xid);
SendInitialEventsForWindow(entry.controls_xid);
SendInitialEventsForWindow(entry.label_xid);
SendInitialEventsForWindow(entry.unselected_label_xid);
return entry;
}
void SendInitialPixmapEventForEntry(size_t entry_index) {
ASSERT_LT(entry_index, entries_.size());
const EntryWindows& entry = entries_[entry_index];
ASSERT_FALSE(wm_->GetWindowOrDie(entry.border_xid)->has_initial_pixmap());
SendSyncRequestProtocolAlarm(entry.border_xid);
ASSERT_FALSE(wm_->GetWindowOrDie(entry.image_xid)->has_initial_pixmap());
SendSyncRequestProtocolAlarm(entry.image_xid);
ASSERT_FALSE(wm_->GetWindowOrDie(entry.controls_xid)->has_initial_pixmap());
SendSyncRequestProtocolAlarm(entry.controls_xid);
ASSERT_FALSE(wm_->GetWindowOrDie(entry.label_xid)->has_initial_pixmap());
SendSyncRequestProtocolAlarm(entry.label_xid);
ASSERT_FALSE(
wm_->GetWindowOrDie(entry.unselected_label_xid)->has_initial_pixmap());
SendSyncRequestProtocolAlarm(entry.unselected_label_xid);
}
void UnmapLoginEntry(int i) {
EntryWindows entry = entries_[i];
entries_.erase(entries_.begin() + i);
// Emulate Chrome behavior, update entries count for all other entries only
// after that remove all windows for the deleted entry.
UpdateEntriesCount(entries_.size());
XEvent event;
if (entry.border_xid) {
xconn_->UnmapWindow(entry.border_xid);
xconn_->InitUnmapEvent(&event, entry.border_xid);
wm_->HandleEvent(&event);
}
if (entry.image_xid) {
xconn_->UnmapWindow(entry.image_xid);
xconn_->InitUnmapEvent(&event, entry.image_xid);
wm_->HandleEvent(&event);
}
if (entry.controls_xid) {
xconn_->UnmapWindow(entry.controls_xid);
xconn_->InitUnmapEvent(&event, entry.controls_xid);
wm_->HandleEvent(&event);
}
if (entry.label_xid) {
xconn_->UnmapWindow(entry.label_xid);
xconn_->InitUnmapEvent(&event, entry.label_xid);
wm_->HandleEvent(&event);
}
if (entry.unselected_label_xid) {
xconn_->UnmapWindow(entry.unselected_label_xid);
xconn_->InitUnmapEvent(&event, entry.unselected_label_xid);
wm_->HandleEvent(&event);
}
}
// Insert new login entry at the specified position.
void InsertLoginEntry(int i) {
// Insert uninitialized entry.
entries_.insert(entries_.begin() + i, EntryWindows());
// Notify all other entries about their new positions.
UpdateEntriesCount(entries_.size());
// Initialize inserted entry.
entries_[i] = CreateLoginEntry(entries_.size(), i);
SendInitialPixmapEventForEntry(i);
}
void UpdateEntriesCount(int num_entries) {
for (size_t i = 0; i < entries_.size(); ++i) {
// Skip just inserted entry without windows.
if (!entries_[i].border_xid)
continue;
vector<int> params;
params.push_back(static_cast<int>(i)); // entry index
params.push_back(num_entries);
params.push_back(kUnselectedImageSize);
params.push_back(kGapBetweenImageAndControls);
wm_->wm_ipc()->SetWindowType(
entries_[i].border_xid,
chromeos::WM_IPC_WINDOW_LOGIN_BORDER,
&params);
SendWindowTypeEvent(entries_[i].border_xid);
}
}
// Selects user entry with the specified index by sending IPC message to
// wm.
void SelectEntry(int index) {
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_SELECT_LOGIN_USER);
msg.set_param(0, index);
SendWmIpcMessage(msg);
}
// Checks if composited window for the specified xid is shown.
bool IsCompositedShown(XWindow xid) const {
Window* window = wm_->GetWindowOrDie(xid);
return window->composited_shown();
}
double GetCompositedOpacity(XWindow xid) const {
Window* window = wm_->GetWindowOrDie(xid);
return window->composited_opacity();
}
// Returns a vector of structures with bounds for all entries.
std::vector<EntryBounds> GetEntriesBounds() {
std::vector<EntryBounds> bounds(entries_.size(), EntryBounds());
for (size_t i = 0; i < entries_.size(); ++i) {
bounds[i].border = GetCompositedWindowBounds(entries_[i].border_xid);
bounds[i].image = GetCompositedWindowBounds(entries_[i].image_xid);
bounds[i].controls = GetCompositedWindowBounds(entries_[i].controls_xid);
bounds[i].label = GetCompositedWindowBounds(entries_[i].label_xid);
bounds[i].unselected_label =
GetCompositedWindowBounds(entries_[i].unselected_label_xid);
}
return bounds;
}
LoginController* login_controller_; // owned by |wm_|
XWindow background_xid_;
XWindow wizard_xid_;
XWindow webui_window_xid_;
vector<EntryWindows> entries_;
};
const int LoginControllerTest::kUnselectedImageSize = 100;
const int LoginControllerTest::kGapBetweenImageAndControls = 5;
const int LoginControllerTest::kImageSize = 260;
const int LoginControllerTest::kControlsSize = 30;
// Check that border windows have shadows but other login windows don't.
TEST_F(LoginControllerTest, Shadow) {
CreateLoginWindows(2, true, true, true);
EXPECT_TRUE(wm_->GetWindowOrDie(entries_[0].border_xid)->shadow() != NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(entries_[0].image_xid)->shadow() == NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(entries_[0].controls_xid)->shadow() == NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(entries_[0].label_xid)->shadow() == NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(entries_[0].unselected_label_xid)->
shadow() == NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(wizard_xid_)->shadow() == NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(background_xid_)->shadow() == NULL);
}
// Check that LoginController does some half-baked handling of transient
// windows that get mapped before Chrome is in a logged-in state.
TEST_F(LoginControllerTest, OtherWindows) {
CreateLoginWindows(2, true, true, true);
const int initial_width = 300;
const int initial_height = 200;
const XWindow xid =
CreateBasicWindow(Rect(0, 0, initial_width, initial_height));
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
info->transient_for = background_xid_;
ASSERT_FALSE(info->mapped);
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
Window* win = wm_->GetWindowOrDie(xid);
MockCompositor::Actor* actor = GetMockActorForWindow(win);
// If LoginManager sees a MapRequest event before Chrome is logged in,
// check that it maps the window centered over its owner.
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(info->mapped);
EXPECT_EQ((wm_->width() - initial_width) / 2, info->bounds.x);
EXPECT_EQ((wm_->height() - initial_height) / 2, info->bounds.y);
EXPECT_EQ(initial_width, info->bounds.width);
EXPECT_EQ(initial_height, info->bounds.height);
// The window should still be in the same spot after it's mapped, and it
// should be visible and have a shadow too.
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_EQ((wm_->width() - initial_width) / 2, info->bounds.x);
EXPECT_EQ((wm_->height() - initial_height) / 2, info->bounds.y);
EXPECT_EQ(initial_width, info->bounds.width);
EXPECT_EQ(initial_height, info->bounds.height);
EXPECT_EQ((wm_->width() - initial_width) / 2, info->bounds.x);
EXPECT_EQ((wm_->height() - initial_height) / 2, info->bounds.y);
EXPECT_EQ(initial_width, actor->GetWidth());
EXPECT_EQ(initial_height, actor->GetHeight());
EXPECT_TRUE(actor->is_shown());
EXPECT_DOUBLE_EQ(1, actor->opacity());
ASSERT_TRUE(win->shadow() != NULL);
EXPECT_TRUE(win->shadow()->is_shown());
// Check that the client is able to move and resize itself.
const int new_x = 40;
const int new_y = 50;
const int new_width = 500;
const int new_height = 400;
xconn_->InitConfigureRequestEvent(
&event, xid, Rect(new_x, new_y, new_width, new_height));
wm_->HandleEvent(&event);
EXPECT_EQ(new_x, info->bounds.x);
EXPECT_EQ(new_y, info->bounds.y);
EXPECT_EQ(new_width, info->bounds.width);
EXPECT_EQ(new_height, info->bounds.height);
xconn_->InitConfigureNotifyEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_EQ(new_x, actor->x());
EXPECT_EQ(new_y, actor->y());
EXPECT_EQ(new_width, actor->GetWidth());
EXPECT_EQ(new_height, actor->GetHeight());
xconn_->InitUnmapEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_FALSE(actor->is_shown());
// Info bubbles shouldn't get shadows.
XWindow info_bubble_xid = CreateSimpleWindow();
ASSERT_TRUE(wm_->wm_ipc()->SetWindowType(
info_bubble_xid,
chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE,
NULL));
xconn_->GetWindowInfoOrDie(info_bubble_xid)->transient_for = background_xid_;
SendInitialEventsForWindow(info_bubble_xid);
EXPECT_TRUE(wm_->GetWindowOrDie(info_bubble_xid)->shadow() == NULL);
// Neither should RGBA windows.
XWindow rgba_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* rgba_info = xconn_->GetWindowInfoOrDie(rgba_xid);
rgba_info->transient_for = background_xid_;
rgba_info->depth = 32;
SendInitialEventsForWindow(rgba_xid);
EXPECT_TRUE(wm_->GetWindowOrDie(rgba_xid)->shadow() == NULL);
// Non-transient non-login windows should be ignored by the login
// controller.
XWindow non_transient_xid = CreateSimpleWindow();
xconn_->InitCreateWindowEvent(&event, non_transient_xid);
wm_->HandleEvent(&event);
xconn_->InitMapRequestEvent(&event, non_transient_xid);
wm_->HandleEvent(&event);
EXPECT_FALSE(xconn_->GetWindowInfoOrDie(non_transient_xid)->mapped);
Window* non_transient_win = wm_->GetWindowOrDie(non_transient_xid);
EXPECT_FALSE(GetMockActorForWindow(non_transient_win)->is_shown());
// Even after the user has logged in, we should continue to manage
// transient windows belonging to login windows.
SetLoggedInState(true);
XWindow post_login_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* post_login_info =
xconn_->GetWindowInfoOrDie(post_login_xid);
post_login_info->transient_for = background_xid_;
SendInitialEventsForWindow(post_login_xid);
Window* post_login_win = wm_->GetWindowOrDie(post_login_xid);
MockCompositor::Actor* post_login_actor =
GetMockActorForWindow(post_login_win);
EXPECT_TRUE(post_login_info->mapped);
EXPECT_TRUE(post_login_actor->is_shown());
}
// Test that the login controller assigns the focus correctly in a few cases.
TEST_F(LoginControllerTest, Focus) {
CreateLoginWindows(3, true, true, false);
// Initially, the first entry's controls window should be focused.
EXPECT_EQ(entries_[0].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[0].controls_xid, GetActiveWindowProperty());
// Click on the second entry's input window.
ASSERT_GE(static_cast<int>(login_controller_->entries_.size()), 2);
SelectEntry(1);
// The second entry should be focused now.
EXPECT_EQ(entries_[1].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[1].controls_xid, GetActiveWindowProperty());
// Now open a non-login window. It should be automatically focused.
const XWindow other_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* other_info =
xconn_->GetWindowInfoOrDie(other_xid);
other_info->transient_for = background_xid_;
SendInitialEventsForWindow(other_xid);
EXPECT_EQ(other_xid, xconn_->focused_xid());
EXPECT_EQ(other_xid, GetActiveWindowProperty());
EXPECT_FALSE(other_info->button_is_grabbed(1));
// Check that override-redirect non-login window (i.e. tooltip) won't be
// focused.
const XWindow override_redirect_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* override_redirect_info =
xconn_->GetWindowInfoOrDie(override_redirect_xid);
override_redirect_info->override_redirect = true;
ASSERT_TRUE(xconn_->MapWindow(override_redirect_xid));
SendInitialEventsForWindow(override_redirect_xid);
EXPECT_NE(override_redirect_xid, xconn_->focused_xid());
EXPECT_NE(override_redirect_xid, GetActiveWindowProperty());
// Button grabs should be installed on the background and controls windows.
MockXConnection::WindowInfo* background_info =
xconn_->GetWindowInfoOrDie(background_xid_);
MockXConnection::WindowInfo* controls_info =
xconn_->GetWindowInfoOrDie(entries_[1].controls_xid);
EXPECT_TRUE(background_info->button_is_grabbed(1));
EXPECT_TRUE(controls_info->button_is_grabbed(1));
// After we click on the background, the second entry's controls window
// should be refocused and a button grab should be installed on the
// non-login window.
XEvent event;
xconn_->set_pointer_grab_xid(background_xid_);
xconn_->InitButtonPressEvent(&event, background_xid_, Point(0, 0), 1);
wm_->HandleEvent(&event);
EXPECT_EQ(entries_[1].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[1].controls_xid, GetActiveWindowProperty());
EXPECT_FALSE(controls_info->button_is_grabbed(1));
EXPECT_TRUE(other_info->button_is_grabbed(1));
}
// Test that the login controller focuses the wizard window when no entries
// are created.
TEST_F(LoginControllerTest, FocusInitialWizardWindow) {
CreateLoginWindows(0, true, true, true);
EXPECT_EQ(wizard_xid_, xconn_->focused_xid());
EXPECT_EQ(wizard_xid_, GetActiveWindowProperty());
}
TEST_F(LoginControllerTest, FocusTransientParent) {
CreateLoginWindows(2, true, true, false);
// When we open a transient dialog, it should get the focus.
const XWindow transient_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* transient_info =
xconn_->GetWindowInfoOrDie(transient_xid);
transient_info->transient_for = entries_[0].controls_xid;
SendInitialEventsForWindow(transient_xid);
EXPECT_EQ(transient_xid, xconn_->focused_xid());
EXPECT_EQ(transient_xid, GetActiveWindowProperty());
// Now open another dialog that's transient for the first dialog.
const XWindow nested_transient_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* nested_transient_info =
xconn_->GetWindowInfoOrDie(nested_transient_xid);
nested_transient_info->transient_for = transient_xid;
SendInitialEventsForWindow(nested_transient_xid);
EXPECT_EQ(nested_transient_xid, xconn_->focused_xid());
EXPECT_EQ(nested_transient_xid, GetActiveWindowProperty());
// If we unmap the nested dialog, the focus should go back to the first
// dialog.
XEvent event;
xconn_->InitUnmapEvent(&event, nested_transient_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(transient_xid, xconn_->focused_xid());
EXPECT_EQ(transient_xid, GetActiveWindowProperty());
// Now unmap the first dialog and check that the focus goes back to the
// controls window.
xconn_->InitUnmapEvent(&event, transient_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(entries_[0].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[0].controls_xid, GetActiveWindowProperty());
// Open a transient dialog, but make it owned by the background window.
const XWindow bg_transient_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* bg_transient_info =
xconn_->GetWindowInfoOrDie(bg_transient_xid);
bg_transient_info->transient_for = background_xid_;
SendInitialEventsForWindow(bg_transient_xid);
EXPECT_EQ(bg_transient_xid, xconn_->focused_xid());
EXPECT_EQ(bg_transient_xid, GetActiveWindowProperty());
// We never want to focus the background. When the dialog gets unmapped,
// we should focus the previously-focused controls window instead.
xconn_->InitUnmapEvent(&event, bg_transient_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(entries_[0].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[0].controls_xid, GetActiveWindowProperty());
}
TEST_F(LoginControllerTest, Modality) {
CreateLoginWindows(2, true, true, false);
const XWindow controls_xid = entries_[0].controls_xid;
MockXConnection::WindowInfo* controls_info =
xconn_->GetWindowInfoOrDie(controls_xid);
// Map a transient window and check that it gets the focus.
const XWindow transient_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* transient_info =
xconn_->GetWindowInfoOrDie(transient_xid);
transient_info->transient_for = entries_[0].controls_xid;
SendInitialEventsForWindow(transient_xid);
ASSERT_EQ(transient_xid, xconn_->focused_xid());
ASSERT_EQ(transient_xid, GetActiveWindowProperty());
// Now ask the WM to make the transient window modal.
XEvent event;
xconn_->InitClientMessageEvent(
&event, transient_xid, xconn_->GetAtomOrDie("_NET_WM_STATE"),
1, xconn_->GetAtomOrDie("_NET_WM_STATE_MODAL"), None, None, None);
wm_->HandleEvent(&event);
ASSERT_TRUE(wm_->GetWindowOrDie(transient_xid)->wm_state_modal());
// Click in the controls window and check that the transient window keeps
// the focus. We also check that the click doesn't get replayed for the
// controls window.
int initial_num_replays = xconn_->num_pointer_ungrabs_with_replayed_events();
xconn_->set_pointer_grab_xid(controls_xid);
xconn_->InitButtonPressEvent(&event, controls_xid, Point(0, 0), 1);
wm_->HandleEvent(&event);
EXPECT_EQ(transient_xid, xconn_->focused_xid());
EXPECT_EQ(transient_xid, GetActiveWindowProperty());
EXPECT_TRUE(controls_info->button_is_grabbed(1));
EXPECT_FALSE(transient_info->button_is_grabbed(1));
EXPECT_EQ(initial_num_replays,
xconn_->num_pointer_ungrabs_with_replayed_events());
}
TEST_F(LoginControllerTest, HideAfterLogin) {
// We should show the windows after they're mapped.
CreateLoginWindows(2, true, true, false);
EXPECT_FALSE(WindowIsOffscreen(background_xid_));
// They should still be shown even after the user logs in.
SetLoggedInState(true);
EXPECT_FALSE(WindowIsOffscreen(background_xid_));
// But we should hide them after the first Chrome window is created.
XWindow xid = CreateToplevelWindow(1, 0, Rect(0, 0, 200, 200));
SendInitialEventsForWindow(xid);
EXPECT_TRUE(WindowIsOffscreen(background_xid_));
}
TEST_F(LoginControllerTest, ShowDestroyedWindows) {
// Create some login windows and then tell the window manager that the
// user has logged in.
CreateLoginWindows(2, true, true, false);
MockCompositor::TexturePixmapActor* background_actor =
GetMockActorForWindow(wm_->GetWindowOrDie(background_xid_));
SetLoggedInState(true);
EXPECT_TRUE(wm_->GetWindow(background_xid_) != NULL);
MockCompositor::StageActor* stage = compositor_->GetDefaultStage();
EXPECT_TRUE(stage->stacked_children()->Contains(background_actor));
// Now unmap and destroy the background window.
XEvent event;
xconn_->InitUnmapEvent(&event, background_xid_);
wm_->HandleEvent(&event);
xconn_->InitDestroyWindowEvent(&event, background_xid_);
wm_->HandleEvent(&event);
// Even though the background window has been destroyed, its actor should
// still be displayed.
EXPECT_TRUE(wm_->GetWindow(background_xid_) == NULL);
EXPECT_TRUE(stage->stacked_children()->Contains(background_actor));
// After the initial browser window gets mapped (but not yet painted), we
// should still show the background.
XWindow xid = CreateToplevelWindow(1, 0, Rect(0, 0, 200, 200));
ConfigureWindowForSyncRequestProtocol(xid);
SendInitialEventsForWindow(xid);
EXPECT_TRUE(stage->stacked_children()->Contains(background_actor));
// After it's painted, the login actors should be destroyed.
SendSyncRequestProtocolAlarm(xid);
EXPECT_FALSE(stage->stacked_children()->Contains(background_actor));
}
TEST_F(LoginControllerTest, SelectGuest) {
// Create two entries for new Chrome.
CreateLoginWindows(2, true, true, false);
// The first entry should initially be focused.
EXPECT_EQ(entries_[0].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[0].controls_xid, GetActiveWindowProperty());
// Click on the entry for the guest window.
SelectEntry(1);
// The guest entry should be focused.
EXPECT_EQ(entries_[1].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[1].controls_xid, GetActiveWindowProperty());
// Click on the first entry.
SelectEntry(0);
// The first entry should be focused.
EXPECT_EQ(entries_[0].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[0].controls_xid, GetActiveWindowProperty());
// Click on the entry for the guest window again.
SelectEntry(1);
// The guest entry should be focused.
EXPECT_EQ(entries_[1].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[1].controls_xid, GetActiveWindowProperty());
// Create wizard window.
wizard_xid_ =
CreateBasicWindow(Rect(0, 0, wm_->width() / 2, wm_->height() / 2));
wm_->wm_ipc()->SetWindowType(wizard_xid_,
chromeos::WM_IPC_WINDOW_LOGIN_GUEST,
NULL);
SendInitialEventsForWindow(wizard_xid_);
// The wizard window should be focused.
EXPECT_EQ(wizard_xid_, xconn_->focused_xid());
EXPECT_EQ(wizard_xid_, GetActiveWindowProperty());
}
TEST_F(LoginControllerTest, RemoveUser) {
// Create 3 entries for new Chrome.
CreateLoginWindows(3, true, true, false);
SelectEntry(1);
EXPECT_EQ(entries_[1].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[1].controls_xid, GetActiveWindowProperty());
UnmapLoginEntry(1);
// Entry 1 was removed from the vector. Focus moved to 0 because 1 is Guest.
EXPECT_EQ(entries_[0].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[0].controls_xid, GetActiveWindowProperty());
UnmapLoginEntry(0);
EXPECT_EQ(entries_[0].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[0].controls_xid, GetActiveWindowProperty());
// Create wizard window.
wizard_xid_ =
CreateBasicWindow(Rect(0, 0, wm_->width() / 2, wm_->height() / 2));
wm_->wm_ipc()->SetWindowType(wizard_xid_,
chromeos::WM_IPC_WINDOW_LOGIN_GUEST,
NULL);
SendInitialEventsForWindow(wizard_xid_);
UnmapLoginEntry(0);
// The wizard window should be focused.
EXPECT_EQ(wizard_xid_, xconn_->focused_xid());
EXPECT_EQ(wizard_xid_, GetActiveWindowProperty());
}
TEST_F(LoginControllerTest, InsertUser) {
// Create 3 entries for new Chrome.
CreateLoginWindows(3, true, true, false);
SelectEntry(1);
EXPECT_EQ(entries_[1].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[1].controls_xid, GetActiveWindowProperty());
// Insert entry after selected one.
InsertLoginEntry(2);
ASSERT_EQ(4, entries_.size());
for (size_t i = 0; i < entries_.size(); i++) {
Window* window = wm_->GetWindowOrDie(entries_[i].border_xid);
EXPECT_EQ(window->type_params()[0], i);
EXPECT_EQ(window->type_params()[1], entries_.size());
}
// The same entry still active.
EXPECT_EQ(entries_[1].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[1].controls_xid, GetActiveWindowProperty());
// Inserted entry is unselected.
EXPECT_TRUE(IsCompositedShown(entries_[2].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[2].image_xid));
EXPECT_FALSE(IsCompositedShown(entries_[2].controls_xid));
EXPECT_FALSE(IsCompositedShown(entries_[2].label_xid));
EXPECT_TRUE(IsCompositedShown(entries_[2].unselected_label_xid));
EXPECT_FLOAT_EQ(0.0, GetCompositedOpacity(entries_[2].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[2].border_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[2].image_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[2].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[2].label_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[2].unselected_label_xid));
// Insert entry just before selected one.
InsertLoginEntry(1);
ASSERT_EQ(5, entries_.size());
for (size_t i = 0; i < entries_.size(); i++) {
Window* window = wm_->GetWindowOrDie(entries_[i].border_xid);
EXPECT_EQ(window->type_params()[0], i);
EXPECT_EQ(window->type_params()[1], entries_.size());
}
// The same entry still active.
EXPECT_EQ(entries_[2].controls_xid, xconn_->focused_xid());
EXPECT_EQ(entries_[2].controls_xid, GetActiveWindowProperty());
// Inserted entry is unselected.
EXPECT_TRUE(IsCompositedShown(entries_[1].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].image_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].controls_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].label_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].unselected_label_xid));
EXPECT_FLOAT_EQ(0.0, GetCompositedOpacity(entries_[1].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].border_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[1].image_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].label_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[1].unselected_label_xid));
}
TEST_F(LoginControllerTest, AreViewsWindowsReady) {
EXPECT_FALSE(login_controller_->views_windows_are_ready_);
// Create 3 entries for new Chrome.
CreateLoginWindows(3, true, true, false);
EXPECT_TRUE(login_controller_->views_windows_are_ready_);
// When all windows for one entry are all unmapped, login screen is
// still considered complete.
UpdateEntriesCount(2);
UnmapLoginEntry(1);
EXPECT_TRUE(login_controller_->views_windows_are_ready_);
// If not all entry windows are unmapped yet, login screen is incomplete.
XEvent event;
ASSERT_NE(0, entries_[0].border_xid);
xconn_->UnmapWindow(entries_[0].border_xid);
xconn_->InitUnmapEvent(&event, entries_[0].border_xid);
wm_->HandleEvent(&event);
EXPECT_FALSE(login_controller_->views_windows_are_ready_);
}
// Test which windows of selected and unselected entry should be off or on
// screen.
TEST_F(LoginControllerTest, ClientOnOffScreen) {
// Create two entries for new Chrome.
CreateLoginWindows(2, true, true, false); // Only need usual entry windows.
// The first entry is selected. Test that controls, image and label
// windows are on screen and the rest windows are off screen.
EXPECT_TRUE(WindowIsOffscreen(entries_[0].border_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[0].image_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[0].controls_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[0].label_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].unselected_label_xid));
// For the second unselected entry, only image and unselected label windows
// must be on screen.
EXPECT_TRUE(WindowIsOffscreen(entries_[1].border_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[1].image_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].label_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[1].unselected_label_xid));
// Click on the second entry to change the selection.
SelectEntry(1);
// Now the same should be checked for both entries but with the second as
// the selected one.
EXPECT_TRUE(WindowIsOffscreen(entries_[1].border_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[1].image_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[1].controls_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[1].label_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].unselected_label_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].border_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[0].image_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].label_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[0].unselected_label_xid));
// Now check that for both entries windows are hidden when login succeeded
// and the first Chrome window is shown.
SetLoggedInState(true);
XWindow xid = CreateToplevelWindow(1, 0, Rect(0, 0, 200, 200));
SendInitialEventsForWindow(xid);
EXPECT_TRUE(WindowIsOffscreen(entries_[0].border_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].image_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].label_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[0].unselected_label_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].border_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].image_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].controls_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].label_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[1].unselected_label_xid));
}
TEST_F(LoginControllerTest, SelectTwice) {
CreateLoginWindows(2, true, true, false);
// The first entry is selected now by default.
EXPECT_TRUE(IsCompositedShown(entries_[0].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].image_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].controls_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].label_xid));
EXPECT_FALSE(IsCompositedShown(entries_[0].unselected_label_xid));
EXPECT_FLOAT_EQ(1.0, GetCompositedOpacity(entries_[0].controls_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].image_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].controls_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].label_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].unselected_label_xid));
EXPECT_FLOAT_EQ(0.0, GetCompositedOpacity(entries_[1].controls_xid));
// Select it again.
SelectEntry(0);
login_controller_->ProcessSelectionChangeCompleted(0);
EXPECT_TRUE(IsCompositedShown(entries_[0].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].image_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].controls_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].label_xid));
EXPECT_FALSE(IsCompositedShown(entries_[0].unselected_label_xid));
EXPECT_FLOAT_EQ(1.0, GetCompositedOpacity(entries_[0].controls_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].image_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].controls_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].label_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].unselected_label_xid));
EXPECT_FLOAT_EQ(0.0, GetCompositedOpacity(entries_[1].controls_xid));
// And again.
SelectEntry(0);
login_controller_->ProcessSelectionChangeCompleted(0);
EXPECT_TRUE(IsCompositedShown(entries_[0].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].image_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].controls_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].label_xid));
EXPECT_FALSE(IsCompositedShown(entries_[0].unselected_label_xid));
EXPECT_FLOAT_EQ(1.0, GetCompositedOpacity(entries_[0].controls_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].border_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].image_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].controls_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].label_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].unselected_label_xid));
EXPECT_FLOAT_EQ(0.0, GetCompositedOpacity(entries_[1].controls_xid));
// Now select the guest entry and check that selection is actually changed.
// Check that image window is hidden for selected guest entry.
SelectEntry(1);
login_controller_->ProcessSelectionChangeCompleted(0);
// Some changes to windows happen when timer shoots which doesn't work when
// running the test manually, so we check for properties that change without
// timer only.
EXPECT_FALSE(IsCompositedShown(entries_[0].label_xid));
EXPECT_FLOAT_EQ(0.0, GetCompositedOpacity(entries_[0].label_xid));
EXPECT_TRUE(IsCompositedShown(entries_[0].unselected_label_xid));
EXPECT_FALSE(IsCompositedShown(entries_[0].controls_xid));
EXPECT_FLOAT_EQ(0.0, GetCompositedOpacity(entries_[0].controls_xid));
EXPECT_TRUE(IsCompositedShown(entries_[1].label_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].image_xid));
EXPECT_FALSE(IsCompositedShown(entries_[1].unselected_label_xid));
}
// Test that we don't crash when Chrome crashes and the login entry windows
// are unmapped in a random order (see http://crosbug.com/5117).
TEST_F(LoginControllerTest, NoCrashOnInconsistenEntry) {
CreateLoginWindows(3, true, true, false);
// Unmap border window for second entry.
XEvent event;
xconn_->UnmapWindow(entries_[1].border_xid);
xconn_->InitUnmapEvent(&event, entries_[1].border_xid);
wm_->HandleEvent(&event);
entries_[1].border_xid = 0;
// Unmap all other windows.
UnmapLoginEntry(0);
UnmapLoginEntry(1);
UnmapLoginEntry(2);
}
// Test that we don't crash if the guest entry is active and an unmap event
// happens for some reason (e.g. Chrome crashes).
TEST_F(LoginControllerTest, NoCrashOnReverseOrderEntryDelete) {
CreateLoginWindows(3, true, true, false);
// Select guest entry.
SelectEntry(2);
// Unmap windows in reverse order.
UnmapLoginEntry(2);
UnmapLoginEntry(1);
UnmapLoginEntry(0);
}
// Test that we don't animate new entry windows getting selected when the
// previously-selected entry is unmapped after the user has logged in.
// (Otherwise, weird animations happen while Chrome is cleaning up right
// before mapping the initial browser window.)
TEST_F(LoginControllerTest, DontSelectEntryAfterLogin) {
CreateLoginWindows(3, true, true, false);
SelectEntry(0);
// Grab the original position of the client window and the actor
// containing the user's picture.
XConnection::WindowGeometry orig_geometry;
ASSERT_TRUE(xconn_->GetWindowGeometry(entries_[1].controls_xid,
&orig_geometry));
MockCompositor::Actor* image_actor =
GetMockActorForWindow(wm_->GetWindowOrDie(entries_[1].image_xid));
const float orig_actor_x = image_actor->x();
const float orig_actor_y = image_actor->y();
const float orig_actor_scale_x = image_actor->scale_x();
const float orig_actor_scale_y = image_actor->scale_y();
// Now tell the WM that we're logged in and unmap the first entry, which
// should result in the second entry getting selected.
SetLoggedInState(true);
UnmapLoginEntry(0);
// Check that the second entry's window and actor didn't get moved.
XConnection::WindowGeometry new_geometry;
ASSERT_TRUE(xconn_->GetWindowGeometry(entries_[1].controls_xid,
&new_geometry));
EXPECT_EQ(orig_geometry.bounds.x, new_geometry.bounds.x);
EXPECT_EQ(orig_geometry.bounds.y, new_geometry.bounds.y);
EXPECT_FLOAT_EQ(orig_actor_x, image_actor->x());
EXPECT_FLOAT_EQ(orig_actor_y, image_actor->y());
EXPECT_FLOAT_EQ(orig_actor_scale_x, image_actor->scale_x());
EXPECT_FLOAT_EQ(orig_actor_scale_y, image_actor->scale_y());
}
TEST_F(LoginControllerTest, ShowEntriesAfterTheyGetPixmaps) {
const int kEntriesCount = 3;
CreateLoginWindows(kEntriesCount, true, false, false);
EXPECT_TRUE(WindowIsOffscreen(background_xid_));
// Begin sending messages that entry windows get pixmaps.
for (int i = 0; i < kEntriesCount; ++i) {
EXPECT_FALSE(login_controller_->views_windows_are_ready_)
<< "Entry index " << i;
EXPECT_TRUE(WindowIsOffscreen(entries_[i].border_xid))
<< "Entry index " << i;
EXPECT_TRUE(WindowIsOffscreen(entries_[i].image_xid))
<< "Entry index " << i;
EXPECT_TRUE(WindowIsOffscreen(entries_[i].controls_xid))
<< "Entry index " << i;
EXPECT_TRUE(WindowIsOffscreen(entries_[i].label_xid))
<< "Entry index " << i;
EXPECT_TRUE(WindowIsOffscreen(entries_[i].unselected_label_xid))
<< "Entry index " << i;
SendInitialPixmapEventForEntry(i);
}
// Check that all needed windows are on the screen.
EXPECT_TRUE(login_controller_->views_windows_are_ready_);
EXPECT_FALSE(WindowIsOffscreen(background_xid_));
for (int i = 0; i < kEntriesCount; ++i) {
EXPECT_FALSE(WindowIsOffscreen(entries_[i].image_xid));
if (i == 0) { // Selected entry.
EXPECT_FALSE(WindowIsOffscreen(entries_[i].controls_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[i].label_xid));
EXPECT_TRUE(WindowIsOffscreen(entries_[i].unselected_label_xid));
} else {
EXPECT_TRUE(WindowIsOffscreen(entries_[i].label_xid));
EXPECT_FALSE(WindowIsOffscreen(entries_[i].unselected_label_xid));
}
}
}
// Test that the login controller handles screen resizes when we are performing
// a WebUI based login
TEST_F(LoginControllerTest, WebUIResize) {
const XWindow root_xid = xconn_->GetRootWindow();
MockXConnection::WindowInfo* root_info = xconn_->GetWindowInfoOrDie(root_xid);
Rect small_bounds = root_info->bounds;
Rect large_bounds(
Point(0, 0), Size(small_bounds.width + 256, small_bounds.height + 256));
// Create a webui window
CreateWebUILoginWindow();
MockXConnection::WindowInfo* webui_info =
xconn_->GetWindowInfoOrDie(webui_window_xid_);
EXPECT_EQ(small_bounds.size(), webui_info->bounds.size());
// Enlarge the root window and check that the webui window gets resized.
xconn_->ResizeWindow(root_xid, large_bounds.size());
XEvent event;
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(large_bounds.size(), webui_info->bounds.size());
// Shrink the root window and check that the webui window gets resized.
xconn_->ResizeWindow(root_xid, small_bounds.size());
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(small_bounds.size(), webui_info->bounds.size());
}
// Test that the login controller handles screen resizes.
TEST_F(LoginControllerTest, Resize) {
const XWindow root_xid = xconn_->GetRootWindow();
MockXConnection::WindowInfo* root_info = xconn_->GetWindowInfoOrDie(root_xid);
Rect small_bounds = root_info->bounds;
Rect large_bounds(
Point(0, 0), Size(small_bounds.width + 256, small_bounds.height + 256));
// Create a background window but no entries or wizard window.
CreateLoginWindows(0, true, false, false);
MockXConnection::WindowInfo* bg_info =
xconn_->GetWindowInfoOrDie(background_xid_);
EXPECT_EQ(small_bounds.size(), bg_info->bounds.size());
// Resize the root window and check that the background gets resized.
xconn_->ResizeWindow(root_xid, large_bounds.size());
XEvent event;
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(large_bounds.size(), bg_info->bounds.size());
// Now create some login entries.
const int kNumEntries = 2;
CreateLoginWindows(kNumEntries, true, true, false);
EXPECT_EQ(large_bounds, bg_info->bounds);
MockXConnection::WindowInfo* first_image_info =
xconn_->GetWindowInfoOrDie(entries_[0].image_xid);
Rect first_image_bounds = first_image_info->bounds;
MockXConnection::WindowInfo* second_image_info =
xconn_->GetWindowInfoOrDie(entries_[1].image_xid);
Rect second_image_bounds = second_image_info->bounds;
// Make the root window smaller. Both entries' image windows should
// shift up and to the left to compensate for the smaller screen size.
xconn_->ResizeWindow(root_xid, small_bounds.size());
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(small_bounds.size(), bg_info->bounds.size());
EXPECT_LT(first_image_info->bounds.x, first_image_bounds.x);
EXPECT_LT(first_image_info->bounds.y, first_image_bounds.y);
EXPECT_LT(second_image_info->bounds.x, second_image_bounds.x);
EXPECT_LT(second_image_info->bounds.y, second_image_bounds.y);
// Unmap all of the entries and create a wizard window. It should be
// centered.
while (!entries_.empty())
UnmapLoginEntry(0);
CreateLoginWindows(0, true, true, true);
MockXConnection::WindowInfo* wizard_info =
xconn_->GetWindowInfoOrDie(wizard_xid_);
EXPECT_EQ(Point((small_bounds.width - wizard_info->bounds.width) / 2,
(small_bounds.height - wizard_info->bounds.height) / 2),
wizard_info->bounds.position());
// Now resize the screen and check that the wizard window is recentered.
xconn_->ResizeWindow(root_xid, large_bounds.size());
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(Point((large_bounds.width - wizard_info->bounds.width) / 2,
(large_bounds.height - wizard_info->bounds.height) / 2),
wizard_info->bounds.position());
}
TEST_F(LoginControllerTest, LoginEntryStackOrder) {
CreateLoginWindows(2, true, true, false);
MockCompositor::StageActor* stage = compositor_->GetDefaultStage();
// entries_[0] --- existing user entry
// entries_[1] --- New User entry
for (size_t i = 0; i < entries_.size(); ++i) {
Window* border = wm_->GetWindowOrDie(entries_[i].border_xid);
Window* image = wm_->GetWindowOrDie(entries_[i].image_xid);
Window* controls = wm_->GetWindowOrDie(entries_[i].controls_xid);
Window* label = wm_->GetWindowOrDie(entries_[i].label_xid);
Window* unselected_label =
wm_->GetWindowOrDie(entries_[i].unselected_label_xid);
// Stacks the windows. The stacking we care about is:
// 1. the image_window is above the border_window;
EXPECT_LT(stage->GetStackingIndex(image->actor()),
stage->GetStackingIndex(border->actor()))
<< "entry: " << i;
// 2. the controls_window is above the border window;
EXPECT_LT(stage->GetStackingIndex(controls->actor()),
stage->GetStackingIndex(border->actor()))
<< "entry: " << i;
// 3. the label_window is above the image_window.
EXPECT_LT(stage->GetStackingIndex(label->actor()),
stage->GetStackingIndex(image->actor()))
<< "entry: " << i;
EXPECT_LT(stage->GetStackingIndex(unselected_label->actor()),
stage->GetStackingIndex(image->actor()))
<< "entry: " << i;
}
}
TEST_F(LoginControllerTest, LoginEntryRelativePositions) {
CreateLoginWindows(2, true, true, false);
std::vector<EntryBounds> bounds = GetEntriesBounds();
// First entry is an existing user/Guest entry and selected, so:
// - image window should be within borders window near the top,
EXPECT_GT(bounds[0].image.left(), bounds[0].border.left());
EXPECT_LT(bounds[0].image.right(), bounds[0].border.right());
EXPECT_GT(bounds[0].image.top(), bounds[0].border.top());
EXPECT_LT(bounds[0].image.bottom(), bounds[0].border.bottom());
// - label should be within image window at the bottom,
EXPECT_GE(bounds[0].label.left(), bounds[0].image.left());
EXPECT_LE(bounds[0].label.right(), bounds[0].image.right());
EXPECT_GT(bounds[0].label.top(), bounds[0].image.top());
EXPECT_LE(bounds[0].label.bottom(), bounds[0].image.bottom());
// - controls window should be within borders window below image window.
EXPECT_GT(bounds[0].controls.left(), bounds[0].border.left());
EXPECT_LT(bounds[0].controls.right(), bounds[0].border.right());
EXPECT_GT(bounds[0].controls.top(), bounds[0].border.top());
EXPECT_LT(bounds[0].controls.bottom(), bounds[0].border.bottom());
EXPECT_GT(bounds[0].controls.top(), bounds[0].image.bottom());
// Second entry is New User entry and is unselected, so:
// - image window should be within borders window in its center,
EXPECT_GT(bounds[1].image.left(), bounds[1].border.left());
EXPECT_LT(bounds[1].image.right(), bounds[1].border.right());
EXPECT_GT(bounds[1].image.top(), bounds[1].border.top());
EXPECT_LT(bounds[1].image.bottom(), bounds[1].border.bottom());
// - label should be within border window.
EXPECT_GE(bounds[1].unselected_label.left(), bounds[1].border.left());
EXPECT_GE(bounds[1].unselected_label.top(), bounds[1].border.top());
EXPECT_LE(bounds[1].unselected_label.bottom(), bounds[1].border.bottom());
// - controls window is hidden, so no check here.
// Now select the guest entry.
SelectEntry(1);
login_controller_->ProcessSelectionChangeCompleted(0);
// First entry is an existing user/Guest entry and unselected, so:
// - image window should be within borders window in its center,
EXPECT_GT(bounds[0].image.left(), bounds[0].border.left());
EXPECT_LT(bounds[0].image.right(), bounds[0].border.right());
EXPECT_GT(bounds[0].image.top(), bounds[0].border.top());
EXPECT_LT(bounds[0].image.bottom(), bounds[0].border.bottom());
// - label should be within image window at the bottom,
EXPECT_GE(bounds[0].unselected_label.left(), bounds[0].image.left());
EXPECT_LE(bounds[0].unselected_label.right(), bounds[0].image.right());
EXPECT_GT(bounds[0].unselected_label.top(), bounds[0].image.top());
EXPECT_LE(bounds[0].unselected_label.bottom(), bounds[0].image.bottom());
// - controls window is hidden, so no check here.
// Second entry is New User entry and is selected, so:
// - image window is hidden, so no need to check for it,
// - label should be within border window,
EXPECT_GE(bounds[1].label.left(), bounds[1].border.left());
EXPECT_GE(bounds[1].label.top(), bounds[1].border.top());
EXPECT_LE(bounds[1].label.bottom(), bounds[1].border.bottom());
// - controls window is should be within border window.
EXPECT_GT(bounds[1].controls.left(), bounds[1].border.left());
EXPECT_LT(bounds[1].controls.right(), bounds[1].border.right());
EXPECT_GT(bounds[1].controls.top(), bounds[1].border.top());
EXPECT_LT(bounds[1].controls.bottom(), bounds[1].border.bottom());
}
// Check that we send a D-Bus message to the session manager once we've started
// the animation to show the login windows.
TEST_F(LoginControllerTest, NotifySessionManagerWhenReady) {
const size_t kInitialNumDBusMessages = dbus_->sent_messages().size();
CreateLoginWindows(2, true, false, true);
EXPECT_EQ(kInitialNumDBusMessages, dbus_->sent_messages().size());
SendInitialPixmapEventForEntry(0);
SendInitialPixmapEventForEntry(1);
ASSERT_EQ(kInitialNumDBusMessages + 1, dbus_->sent_messages().size());
const MockDBusInterface::Message& msg = dbus_->sent_messages()[0];
EXPECT_EQ(login_manager::kSessionManagerServiceName, msg.target);
EXPECT_EQ(login_manager::kSessionManagerServicePath, msg.object);
EXPECT_EQ(login_manager::kSessionManagerInterface, msg.interface);
EXPECT_EQ(login_manager::kSessionManagerEmitLoginPromptVisible, msg.method);
}
// Testing that we signal the SessionManager when the WebUI Login window has
// been created
TEST_F(LoginControllerTest, NotifySessionManagerWhenWebUIReady) {
const size_t kInitialNumDBusMessages = dbus_->sent_messages().size();
CreateWebUILoginWindow();
ASSERT_EQ(kInitialNumDBusMessages + 1, dbus_->sent_messages().size());
const MockDBusInterface::Message& msg = dbus_->sent_messages()[0];
EXPECT_EQ(login_manager::kSessionManagerServiceName, msg.target);
EXPECT_EQ(login_manager::kSessionManagerServicePath, msg.object);
EXPECT_EQ(login_manager::kSessionManagerInterface, msg.interface);
EXPECT_EQ(login_manager::kSessionManagerEmitLoginPromptVisible, msg.method);
}
// Test handling of WindowMapRequests for WebUI Login Window
TEST_F(LoginControllerTest, HandleWindowMapRequestsWebUILoginWindow) {
EXPECT_FALSE(login_controller_->IsWebUIWindowReady());
CreateWebUILoginWindow();
EXPECT_TRUE(login_controller_->IsWebUIWindowReady());
EXPECT_EQ(webui_window_xid_, xconn_->focused_xid());
EXPECT_EQ(webui_window_xid_, GetActiveWindowProperty());
}
// Check that when we see duplicate wizard windows, we unregister our interest
// in the first one when the second one is mapped so that we won't fail an
// assert if the first window is destroyed later. This is just a shot in the
// dark as a possible cause of http://crosbug.com/18809.
TEST_F(LoginControllerTest, HandleDuplicateWindows) {
CreateWebUILoginWindow();
XWindow wizard_xid1 = CreateSimpleWindow();
wm_->wm_ipc()->SetWindowType(
wizard_xid1, chromeos::WM_IPC_WINDOW_LOGIN_GUEST, NULL);
SendInitialEventsForWindow(wizard_xid1);
XWindow wizard_xid2 = CreateSimpleWindow();
wm_->wm_ipc()->SetWindowType(
wizard_xid2, chromeos::WM_IPC_WINDOW_LOGIN_GUEST, NULL);
SendInitialEventsForWindow(wizard_xid2);
XEvent event;
xconn_->UnmapWindow(wizard_xid1);
xconn_->InitUnmapEvent(&event, wizard_xid1);
wm_->HandleEvent(&event);
xconn_->InitDestroyWindowEvent(&event, wizard_xid1);
wm_->HandleEvent(&event);
}
// Test that we focus the first controls window as soon as we map it.
TEST_F(LoginControllerTest, FocusFirstControlsWindowImmediately) {
// Create just a background window.
CreateLoginWindows(0, true, false, false);
// Create a border window for the first entry.
XWindow border_xid =
CreateBasicWindow(
Rect(0, 0,
kImageSize + 2 * kGapBetweenImageAndControls,
kImageSize + kControlsSize + 3 * kGapBetweenImageAndControls));
vector<int> params;
params.push_back(0); // entry index
params.push_back(1); // num entries
params.push_back(kUnselectedImageSize);
params.push_back(kGapBetweenImageAndControls);
wm_->wm_ipc()->SetWindowType(
border_xid,
chromeos::WM_IPC_WINDOW_LOGIN_BORDER,
&params);
SendInitialEventsForWindow(border_xid);
// Now create a controls window for the entry. Don't map it yet.
XWindow controls_xid =
CreateBasicWindow(Rect(0, 0, kImageSize, kControlsSize));
ConfigureWindowForSyncRequestProtocol(controls_xid);
params.clear();
params.push_back(0); // entry index
wm_->wm_ipc()->SetWindowType(
controls_xid,
chromeos::WM_IPC_WINDOW_LOGIN_CONTROLS,
&params);
XEvent event;
xconn_->InitCreateWindowEvent(&event, controls_xid);
wm_->HandleEvent(&event);
// As soon as we send a map request, the controls window should be focused.
EXPECT_EQ(0, xconn_->focused_xid());
xconn_->InitMapRequestEvent(&event, controls_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(controls_xid, xconn_->focused_xid());
EXPECT_EQ(controls_xid, GetActiveWindowProperty());
}
#ifndef TOUCH_UI
TEST_F(LoginControllerTest, UnhideCursorOnLeave) {
// At startup, we should hide the cursor and map a fullscreen input window.
EXPECT_FALSE(xconn_->cursor_shown());
XWindow hide_mouse_cursor_xid = login_controller_->hide_mouse_cursor_xid_;
ASSERT_TRUE(hide_mouse_cursor_xid != 0);
MockXConnection::WindowInfo* info =
xconn_->GetWindowInfoOrDie(hide_mouse_cursor_xid);
EXPECT_EQ(wm_->root_bounds(), info->bounds);
EXPECT_TRUE(info->input_only);
EXPECT_TRUE(info->mapped);
// The window should be destroyed and the cursor shown as soon as the mouse
// moves.
XEvent event;
xconn_->InitMotionNotifyEvent(&event, hide_mouse_cursor_xid, Point(0, 0));
wm_->HandleEvent(&event);
EXPECT_TRUE(xconn_->cursor_shown());
EXPECT_TRUE(xconn_->GetWindowInfo(hide_mouse_cursor_xid) == NULL);
}
TEST_F(LoginControllerTest, UnhideCursorOnBrowserWindowVisible) {
// We should create a window to hide the cursor at startup.
EXPECT_FALSE(xconn_->cursor_shown());
XWindow hide_mouse_cursor_xid = login_controller_->hide_mouse_cursor_xid_;
ASSERT_TRUE(hide_mouse_cursor_xid != 0);
EXPECT_TRUE(xconn_->GetWindowInfo(hide_mouse_cursor_xid) != NULL);
// It should still be there after the login windows are created...
CreateLoginWindows(2, true, true, true);
EXPECT_FALSE(xconn_->cursor_shown());
EXPECT_TRUE(xconn_->GetWindowInfo(hide_mouse_cursor_xid) != NULL);
// ... and after the user logs in...
SetLoggedInState(true);
EXPECT_FALSE(xconn_->cursor_shown());
EXPECT_TRUE(xconn_->GetWindowInfo(hide_mouse_cursor_xid) != NULL);
// ... and after the first browser window is mapped.
XWindow browser_xid =
CreateToplevelWindow(1, 0, Rect(0, 0, 200, 200));
ConfigureWindowForSyncRequestProtocol(browser_xid);
SendInitialEventsForWindow(browser_xid);
EXPECT_FALSE(xconn_->cursor_shown());
EXPECT_TRUE(xconn_->GetWindowInfo(hide_mouse_cursor_xid) != NULL);
// Once the browser window is visible, it should be destroyed.
SendSyncRequestProtocolAlarm(browser_xid);
EXPECT_TRUE(xconn_->cursor_shown());
EXPECT_TRUE(xconn_->GetWindowInfo(hide_mouse_cursor_xid) == NULL);
}
#endif // !defined(TOUCH_UI)
// Test that we don't double-register our interest in taking ownership of a
// login window's actor after the login window is destroyed, if said window gets
// remapped. See http://crosbug.com/13093.
TEST_F(LoginControllerTest, OnlyRegisterOnceForDestroyedWindow) {
CreateLoginWindows(2, true, true, true);
XEvent event;
xconn_->UnmapWindow(wizard_xid_);
xconn_->InitUnmapEvent(&event, wizard_xid_);
wm_->HandleEvent(&event);
xconn_->InitMapRequestEvent(&event, wizard_xid_);
wm_->HandleEvent(&event);
}
} // namespace window_manager
int main(int argc, char** argv) {
return window_manager::InitAndRunTests(&argc, argv, &FLAGS_logtostderr);
}