| // 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, |
| ¶ms); |
| wm_->wm_ipc()->SetWindowType( |
| entry.controls_xid, |
| chromeos::WM_IPC_WINDOW_LOGIN_CONTROLS, |
| ¶ms); |
| wm_->wm_ipc()->SetWindowType( |
| entry.label_xid, |
| chromeos::WM_IPC_WINDOW_LOGIN_LABEL, |
| ¶ms); |
| wm_->wm_ipc()->SetWindowType( |
| entry.unselected_label_xid, |
| chromeos::WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL, |
| ¶ms); |
| |
| // 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, |
| ¶ms); |
| |
| 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, |
| ¶ms); |
| 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, |
| ¶ms); |
| 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, |
| ¶ms); |
| 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); |
| } |