blob: fcf07e4130724ee306415e0a196974efd53cc510 [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 <algorithm>
#include <cstdarg>
#include <set>
#include <tr1/memory>
#include <vector>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "cros/chromeos_wm_ipc_enums.h"
#include "window_manager/compositor/compositor.h"
#include "window_manager/event_consumer.h"
#include "window_manager/event_loop.h"
#include "window_manager/geometry.h"
#include "window_manager/panels/panel.h"
#include "window_manager/panels/panel_bar.h"
#include "window_manager/panels/panel_manager.h"
#include "window_manager/shadow.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)");
// From window_manager.cc.
DECLARE_bool(unredirect_fullscreen_window);
DECLARE_string(logged_in_log_dir);
DECLARE_string(logged_out_log_dir);
using base::TimeDelta;
using file_util::FileEnumerator;
using std::find;
using std::set;
using std::string;
using std::tr1::shared_ptr;
using std::vector;
using window_manager::util::CreateTimeTicksFromMs;
using window_manager::util::GetCurrentTimeSec;
using window_manager::util::GetMonotonicTime;
using window_manager::util::SetCurrentTimeForTest;
using window_manager::util::SetMonotonicTimeForTest;
namespace window_manager {
class WindowManagerTest : public BasicWindowManagerTest {
protected:
// Recursively walk a directory and return the total size of all files
// within it.
off_t GetTotalFileSizeInDirectory(const FilePath& dir_path) {
off_t total_size = 0;
FileEnumerator enumerator(dir_path, true, FileEnumerator::FILES);
while (true) {
FilePath file_path = enumerator.Next();
if (file_path.value().empty())
break;
FileEnumerator::FindInfo info;
enumerator.GetFindInfo(&info);
total_size += info.stat.st_size;
}
return total_size;
}
};
TEST_F(WindowManagerTest, RegisterExistence) {
// First, make sure that the window manager created a window and gave it
// a title.
XAtom title_atom = None;
ASSERT_TRUE(xconn_->GetAtom("_NET_WM_NAME", &title_atom));
string window_title;
EXPECT_TRUE(
xconn_->GetStringProperty(wm_->wm_xid_, title_atom, &window_title));
EXPECT_EQ(WindowManager::GetWmName(), window_title);
// Check that the window and compositing manager selections are owned by
// the window manager's window.
XAtom wm_atom = None, cm_atom = None;
ASSERT_TRUE(xconn_->GetAtom("WM_S0", &wm_atom));
ASSERT_TRUE(xconn_->GetAtom("_NET_WM_CM_S0", &cm_atom));
EXPECT_EQ(wm_->wm_xid_, xconn_->GetSelectionOwner(wm_atom));
EXPECT_EQ(wm_->wm_xid_, xconn_->GetSelectionOwner(cm_atom));
XAtom manager_atom = None;
ASSERT_TRUE(xconn_->GetAtom("MANAGER", &manager_atom));
// Client messages should be sent to the root window announcing the
// window manager's existence.
MockXConnection::WindowInfo* root_info =
xconn_->GetWindowInfoOrDie(xconn_->GetRootWindow());
ASSERT_GE(root_info->client_messages.size(), 2);
EXPECT_EQ(ClientMessage, root_info->client_messages[0].type);
EXPECT_EQ(manager_atom, root_info->client_messages[0].message_type);
EXPECT_EQ(XConnection::kLongFormat, root_info->client_messages[0].format);
EXPECT_EQ(wm_atom, root_info->client_messages[0].data.l[1]);
EXPECT_EQ(wm_->wm_xid_, root_info->client_messages[0].data.l[2]);
EXPECT_EQ(ClientMessage, root_info->client_messages[1].type);
EXPECT_EQ(manager_atom, root_info->client_messages[1].message_type);
EXPECT_EQ(XConnection::kLongFormat, root_info->client_messages[1].format);
EXPECT_EQ(cm_atom, root_info->client_messages[1].data.l[1]);
EXPECT_EQ(wm_->wm_xid_, root_info->client_messages[0].data.l[2]);
}
// Test different race conditions where a client window is created and/or
// mapped while WindowManager::Init() is running.
TEST_F(WindowManagerTest, ExistingWindows) {
// First, test the case where a window has already been mapped before the
// WindowManager object is initialized, so no CreateNotify or MapNotify
// event is sent.
wm_.reset(NULL);
xconn_.reset(new MockXConnection);
SetLoggedInState(true);
RegisterCommonKeySyms();
event_loop_.reset(new EventLoop);
compositor_.reset(new MockCompositor(xconn_.get()));
XWindow xid = CreateSimpleWindow();
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
xconn_->MapWindow(xid);
CreateAndInitNewWm();
Window* win = wm_->GetWindowOrDie(xid);
EXPECT_TRUE(win->mapped());
EXPECT_TRUE(GetMockActorForWindow(win)->is_shown());
// Now test the case where the window starts out unmapped and
// WindowManager misses the CreateNotify event but receives the
// MapRequest (and subsequent MapNotify).
wm_.reset(NULL);
xconn_.reset(new MockXConnection);
SetLoggedInState(true);
RegisterCommonKeySyms();
event_loop_.reset(new EventLoop);
compositor_.reset(new MockCompositor(xconn_.get()));
xid = CreateSimpleWindow();
info = xconn_->GetWindowInfoOrDie(xid);
CreateAndInitNewWm();
EXPECT_FALSE(info->mapped);
win = wm_->GetWindowOrDie(xid);
EXPECT_FALSE(win->mapped());
EXPECT_FALSE(GetMockActorForWindow(win)->is_shown());
XEvent event;
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(info->mapped);
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(win->mapped());
EXPECT_TRUE(GetMockActorForWindow(win)->is_shown());
// Finally, test the typical case where a window is created after
// WindowManager has been initialized.
wm_.reset(NULL);
xconn_.reset(new MockXConnection);
SetLoggedInState(true);
RegisterCommonKeySyms();
event_loop_.reset(new EventLoop);
compositor_.reset(new MockCompositor(xconn_.get()));
xid = None;
info = NULL;
CreateAndInitNewWm();
xid = CreateSimpleWindow();
info = xconn_->GetWindowInfoOrDie(xid);
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_FALSE(info->mapped);
win = wm_->GetWindowOrDie(xid);
EXPECT_FALSE(win->mapped());
EXPECT_FALSE(GetMockActorForWindow(win)->is_shown());
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(info->mapped);
EXPECT_TRUE(win->mapped());
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(GetMockActorForWindow(win)->is_shown());
}
// Test that we display override-redirect windows onscreen regardless of
// whether they're mapped or not by the time that we learn about them.
TEST_F(WindowManagerTest, OverrideRedirectMapping) {
// Test the case where a client has already mapped an override-redirect
// window by the time that we receive the CreateNotify event about it.
// We should still pay attention to the MapNotify event that comes
// afterwards and display the window.
XWindow xid = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(10, 20, 30, 40),
true, // override redirect
false, // input only
0, 0); // event mask, visual
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
xconn_->MapWindow(xid);
ASSERT_TRUE(info->mapped);
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
// Now test the other possibility, where the window isn't mapped on the X
// server yet when we receive the CreateNotify event.
Window* win = wm_->GetWindowOrDie(xid);
EXPECT_TRUE(GetMockActorForWindow(win)->is_shown());
XWindow xid2 = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(10, 20, 30, 40),
true, // override redirect
false, // input only
0, 0); // event mask, visual
MockXConnection::WindowInfo* info2 = xconn_->GetWindowInfoOrDie(xid2);
xconn_->InitCreateWindowEvent(&event, xid2);
wm_->HandleEvent(&event);
xconn_->MapWindow(xid2);
ASSERT_TRUE(info2->mapped);
xconn_->InitMapEvent(&event, xid2);
wm_->HandleEvent(&event);
Window* win2 = wm_->GetWindowOrDie(xid2);
EXPECT_TRUE(GetMockActorForWindow(win2)->is_shown());
}
TEST_F(WindowManagerTest, InputWindows) {
// Check that CreateInputWindow() creates windows as requested.
int event_mask = ButtonPressMask | ButtonReleaseMask;
XWindow xid = wm_->CreateInputWindow(Rect(100, 200, 300, 400), event_mask);
MockXConnection::WindowInfo* info = xconn_->GetWindowInfo(xid);
ASSERT_TRUE(info != NULL);
EXPECT_EQ(100, info->bounds.x);
EXPECT_EQ(200, info->bounds.y);
EXPECT_EQ(300, info->bounds.width);
EXPECT_EQ(400, info->bounds.height);
EXPECT_TRUE(info->mapped);
EXPECT_TRUE(info->override_redirect);
EXPECT_EQ(event_mask, info->event_mask);
// Move and resize the window.
EXPECT_TRUE(wm_->ConfigureInputWindow(xid, Rect(500, 600, 700, 800)));
EXPECT_EQ(500, info->bounds.x);
EXPECT_EQ(600, info->bounds.y);
EXPECT_EQ(700, info->bounds.width);
EXPECT_EQ(800, info->bounds.height);
EXPECT_TRUE(info->mapped);
}
TEST_F(WindowManagerTest, EventConsumer) {
TestEventConsumer ec;
wm_->event_consumers_.insert(&ec);
XWindow xid = CreateSimpleWindow();
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
wm_->RegisterEventConsumerForWindowEvents(xid, &ec);
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
// Send various events to the WindowManager object and check that they
// get forwarded to our EventConsumer.
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(info->mapped);
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
xconn_->InitButtonPressEvent(&event, xid, Point(5, 5), 1);
wm_->HandleEvent(&event);
xconn_->InitUnmapEvent(&event, xid);
wm_->HandleEvent(&event);
wm_->UnregisterEventConsumerForWindowEvents(xid, &ec);
// We don't know whether we'll get the MapRequest event for this window;
// the layout manager might've handled it before us.
EXPECT_EQ(1, ec.num_mapped_windows());
EXPECT_EQ(1, ec.num_button_presses());
EXPECT_EQ(1, ec.num_unmapped_windows());
// Create a second window.
ec.reset_stats();
XWindow xid2 = CreateSimpleWindow();
MockXConnection::WindowInfo* info2 = xconn_->GetWindowInfoOrDie(xid2);
info2->override_redirect = true;
// Send events appropriate for an override-redirect window.
xconn_->InitCreateWindowEvent(&event, xid2);
wm_->HandleEvent(&event);
xconn_->InitMapEvent(&event, xid2);
wm_->HandleEvent(&event);
xconn_->InitButtonPressEvent(&event, xid2, Point(5, 5), 1);
wm_->HandleEvent(&event);
xconn_->InitUnmapEvent(&event, xid2);
wm_->HandleEvent(&event);
// The event consumer should've heard about the second window being
// mapped and unmapped, but not about the button press (since it never
// registered interest in the window).
EXPECT_EQ(1, ec.num_mapped_windows());
EXPECT_EQ(0, ec.num_button_presses());
EXPECT_EQ(1, ec.num_unmapped_windows());
// It's a bit of a stretch to include this in this test, but check that the
// window manager didn't do anything to the window (since it's an
// override-redirect window).
EXPECT_FALSE(info2->changed);
// Create a third window. Set a big, bogus window type on it so that
// none of the standard event consumers try to do anything with it.
ec.reset_stats();
ec.set_should_return_true_for_map_requests(true);
XWindow xid3 = CreateSimpleWindow();
wm_->wm_ipc()->SetWindowType(
xid3, static_cast<chromeos::WmIpcWindowType>(4243289), NULL);
xconn_->InitCreateWindowEvent(&event, xid3);
wm_->HandleEvent(&event);
xconn_->InitMapRequestEvent(&event, xid3);
wm_->HandleEvent(&event);
// We should get a map request for this window, and we should immediately
// get notified that it was mapped (since we returned true in response to
// the request).
EXPECT_EQ(1, ec.num_map_requests());
EXPECT_EQ(1, ec.num_mapped_windows());
EXPECT_TRUE(wm_->GetWindowOrDie(xid3)->mapped());
// Check that we don't get notified again when the window manager
// receives notification that the window was mapped.
xconn_->InitMapEvent(&event, xid3);
wm_->HandleEvent(&event);
EXPECT_EQ(1, ec.num_mapped_windows());
// Create a window that'll get mapped by LayoutManager2. Send two
// MapRequests for it (see http://crosbug.com/4176), and check that our
// event consumer only gets notified about the first one.
ec.reset_stats();
XWindow xid4 = CreateSimpleWindow();
xconn_->InitCreateWindowEvent(&event, xid4);
wm_->HandleEvent(&event);
xconn_->InitMapRequestEvent(&event, xid4);
wm_->HandleEvent(&event);
xconn_->InitMapRequestEvent(&event, xid4);
wm_->HandleEvent(&event);
EXPECT_EQ(1, ec.num_mapped_windows());
}
// Check that windows that get reparented away from the root (like Flash
// plugin windows) get unredirected.
TEST_F(WindowManagerTest, Reparent) {
XWindow xid = CreateSimpleWindow();
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
ASSERT_TRUE(info->redirected);
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(info->mapped);
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
XReparentEvent* reparent_event = &(event.xreparent);
memset(reparent_event, 0, sizeof(*reparent_event));
reparent_event->type = ReparentNotify;
reparent_event->window = xid;
reparent_event->parent = 324324; // arbitrary number
wm_->HandleEvent(&event);
// After the window gets reparented away from the root, WindowManager
// should've unredirected it and should no longer be tracking it.
EXPECT_TRUE(wm_->GetWindow(xid) == NULL);
EXPECT_FALSE(info->redirected);
}
TEST_F(WindowManagerTest, OverrideRedirectConfigureNotify) {
MockCompositor::StageActor* stage = compositor_->GetDefaultStage();
XEvent event;
// Use the _NET_WM_WINDOW_TYPE_MENU hint to make the windows have shadows.
const XAtom win_type_xatom = xconn_->GetAtomOrDie("_NET_WM_WINDOW_TYPE");
const XAtom atom_xatom = xconn_->GetAtomOrDie("ATOM");
const XAtom menu_xatom = xconn_->GetAtomOrDie("_NET_WM_WINDOW_TYPE_MENU");
// Create two override-redirect windows and map them both.
XWindow xid = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(10, 20, 30, 40),
true, // override redirect
false, // input only
0, 0); // event mask, visual
xconn_->SetIntProperty(xid, win_type_xatom, atom_xatom, menu_xatom);
xconn_->MapWindow(xid);
SendInitialEventsForWindow(xid);
Window* win = wm_->GetWindowOrDie(xid);
ASSERT_TRUE(win->shadow() != NULL);
XWindow xid2 = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(10, 20, 30, 40),
true, // override redirect
false, // input only
0, 0); // event mask, visual
xconn_->SetIntProperty(xid2, win_type_xatom, atom_xatom, menu_xatom);
xconn_->MapWindow(xid2);
SendInitialEventsForWindow(xid2);
Window* win2 = wm_->GetWindowOrDie(xid2);
ASSERT_TRUE(win2->shadow() != NULL);
// The second window should initially be stacked above the first, and
// each window's shadow should be stacked under the window.
EXPECT_LT(stage->GetStackingIndex(win2->actor()),
stage->GetStackingIndex(win2->shadow()->group()));
EXPECT_LT(stage->GetStackingIndex(win2->shadow()->group()),
stage->GetStackingIndex(win->actor()));
EXPECT_LT(stage->GetStackingIndex(win->actor()),
stage->GetStackingIndex(win->shadow()->group()));
// Send a message saying that the first window is on top of the second.
xconn_->StackWindow(xid, xid2, true);
xconn_->InitConfigureNotifyEvent(&event, xid);
event.xconfigure.above = xid2;
wm_->HandleEvent(&event);
EXPECT_LT(stage->GetStackingIndex(win->actor()),
stage->GetStackingIndex(win->shadow()->group()));
EXPECT_LT(stage->GetStackingIndex(win->shadow()->group()),
stage->GetStackingIndex(win2->actor()));
EXPECT_LT(stage->GetStackingIndex(win2->actor()),
stage->GetStackingIndex(win2->shadow()->group()));
// Move and resize the first window and check that its actor is updated
// correspondingly.
const Rect kNewBounds(300, 400, 60, 70);
xconn_->ConfigureWindow(xid, kNewBounds);
xconn_->InitConfigureNotifyEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_EQ(kNewBounds, win->actor()->GetBounds());
}
TEST_F(WindowManagerTest, StackOverrideRedirectWindowsAboveLayers) {
MockCompositor::StageActor* stage = compositor_->GetDefaultStage();
XEvent event;
// Create a normal, non-override-redirect window.
XWindow normal_xid = CreateSimpleWindow();
SendInitialEventsForWindow(normal_xid);
Window* normal_win = wm_->GetWindowOrDie(normal_xid);
// Create an override-redirect window and map it.
XWindow xid = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(10, 20, 30, 40),
true, // override redirect
false, // input only
0, 0); // event mask, visual
xconn_->MapWindow(xid);
SendInitialEventsForWindow(xid);
Window* win = wm_->GetWindowOrDie(xid);
// The override-redirect window's actor should initially be stacked below the
// layers above LAYER_TOP_CLIENT_WINDOW -- these layers are reserved for
// actors that should always show up above client windows.
Compositor::Actor* non_client_window_layer_actor =
wm_->stacking_manager()->layer_to_actor_[
static_cast<StackingManager::Layer>(
StackingManager::LAYER_TOP_CLIENT_WINDOW - 1)].get();
ASSERT_TRUE(non_client_window_layer_actor != NULL);
EXPECT_LT(stage->GetStackingIndex(non_client_window_layer_actor),
stage->GetStackingIndex(win->actor()));
// It should also be somewhere above the normal window.
EXPECT_LT(stage->GetStackingIndex(win->actor()),
stage->GetStackingIndex(normal_win->actor()));
// Stack the override-redirect window slightly lower, but still above the
// normal window.
XWindow fullscreen_layer_xid =
wm_->stacking_manager()->layer_to_xid_[
StackingManager::LAYER_FULLSCREEN_WINDOW];
xconn_->StackWindow(xid, fullscreen_layer_xid, true);
xconn_->InitConfigureNotifyEvent(&event, xid);
event.xconfigure.above = fullscreen_layer_xid;
wm_->HandleEvent(&event);
// Create a second normal window and check that the override-redirect
// window is above it. This protects against a regression of the issue
// described at http://crosbug.com/3451.
XWindow normal_xid2 = CreateSimpleWindow();
SendInitialEventsForWindow(normal_xid2);
Window* normal_win2 = wm_->GetWindowOrDie(normal_xid2);
EXPECT_LT(stage->GetStackingIndex(win->actor()),
stage->GetStackingIndex(normal_win->actor()));
EXPECT_LT(stage->GetStackingIndex(win->actor()),
stage->GetStackingIndex(normal_win2->actor()));
}
// Test that we honor ConfigureRequest events that change an unmapped window's
// size or position, and that we ignore fields that are unset in its
// |value_mask| field.
TEST_F(WindowManagerTest, ConfigureRequestBeforeMap) {
XWindow xid = CreateSimpleWindow();
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
const Size kOrigSize = info->bounds.size();
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
// Send a ConfigureRequest event with its width and height fields masked
// out, and check that the new width and height values are ignored.
const Size kNewSize(kOrigSize.width * 2, kOrigSize.height * 2);
xconn_->InitConfigureRequestEvent(
&event, xid, Rect(info->bounds.position(), kNewSize));
event.xconfigurerequest.value_mask &= ~(CWWidth | CWHeight);
wm_->HandleEvent(&event);
EXPECT_EQ(kOrigSize, info->bounds.size());
// Now turn on the width bit and check that it gets applied.
event.xconfigurerequest.value_mask |= CWWidth;
wm_->HandleEvent(&event);
EXPECT_EQ(Size(kNewSize.width, kOrigSize.height), info->bounds.size());
// Turn on the height bit as well.
event.xconfigurerequest.value_mask |= CWHeight;
wm_->HandleEvent(&event);
EXPECT_EQ(kNewSize, info->bounds.size());
// Send a request to move the window and check that both the X and composited
// positions are updated. See http://crosbug.com/14075.
const Point kNewPosition(info->bounds.x + 10, info->bounds.y + 20);
xconn_->InitConfigureRequestEvent(
&event, xid, Rect(kNewPosition, Size(0, 0)));
event.xconfigurerequest.value_mask = (CWX | CWY);
wm_->HandleEvent(&event);
EXPECT_EQ(kNewPosition, info->bounds.position());
EXPECT_EQ(kNewPosition,
wm_->GetWindowOrDie(xid)->actor()->GetBounds().position());
// Now map and unmap the window.
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
ASSERT_TRUE(info->mapped);
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
ASSERT_TRUE(xconn_->UnmapWindow(xid));
xconn_->InitUnmapEvent(&event, xid);
wm_->HandleEvent(&event);
// Try to configure the window again. This is testing against a regression of
// the failed assert from http://crosbug.com/17376.
const Rect kNewBounds(
Point(kNewPosition.x + 30, kNewPosition.y + 40), kNewSize);
xconn_->InitConfigureRequestEvent(&event, xid, kNewBounds);
wm_->HandleEvent(&event);
EXPECT_EQ(kNewBounds.position(),
wm_->GetWindowOrDie(xid)->composited_origin());
// There's no guarantee that the X window will be moved -- if the window's
// visibility was set to VISIBILITY_HIDDEN when it was unmapped, only the
// actor will be moved.
}
TEST_F(WindowManagerTest, ResizeScreen) {
// Look up EWMH atoms relating to the screen size.
XAtom geometry_atom = None;
ASSERT_TRUE(xconn_->GetAtom("_NET_DESKTOP_GEOMETRY", &geometry_atom));
XAtom workarea_atom = None;
ASSERT_TRUE(xconn_->GetAtom("_NET_WORKAREA", &workarea_atom));
XWindow root_xid = xconn_->GetRootWindow();
MockXConnection::WindowInfo* root_info = xconn_->GetWindowInfoOrDie(root_xid);
// Check that they're set correctly.
EXPECT_TRUE(
TestIntArrayProperty(root_xid, geometry_atom, 2,
root_info->bounds.width, root_info->bounds.height));
EXPECT_TRUE(
TestIntArrayProperty(
root_xid, workarea_atom, 4,
0, 0, root_info->bounds.width, root_info->bounds.height));
int new_width = root_info->bounds.width / 2;
int new_height = root_info->bounds.height / 2;
// Resize the root and compositing overlay windows to half their size.
root_info->bounds.width = new_width;
root_info->bounds.height = new_height;
MockXConnection::WindowInfo* overlay_info =
xconn_->GetWindowInfoOrDie(xconn_->GetCompositingOverlayWindow(root_xid));
overlay_info->bounds.width = new_width;
overlay_info->bounds.height = new_height;
// Send the WM an event saying that the screen has been resized.
XEvent event;
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(new_width, wm_->width());
EXPECT_EQ(new_height, wm_->height());
EXPECT_EQ(new_width, wm_->stage()->GetWidth());
EXPECT_EQ(new_height, wm_->stage()->GetHeight());
// EWMH properties on the root window should be updated as well.
EXPECT_TRUE(
TestIntArrayProperty(root_xid, geometry_atom, 2, new_width, new_height));
EXPECT_TRUE(
TestIntArrayProperty(root_xid, workarea_atom, 4,
0, 0, new_width, new_height));
}
// Test that the _NET_WORKAREA property on the root window excludes areas
// used for panel docks.
TEST_F(WindowManagerTest, SubtractPanelDocksFromNetWorkareaProperty) {
// The _NET_WORKAREA property should initially cover the dimensions of
// the screen.
XAtom workarea_atom = None;
ASSERT_TRUE(xconn_->GetAtom("_NET_WORKAREA", &workarea_atom));
XWindow root_xid = xconn_->GetRootWindow();
MockXConnection::WindowInfo* root_info = xconn_->GetWindowInfoOrDie(root_xid);
EXPECT_TRUE(
TestIntArrayProperty(
root_xid, workarea_atom, 4,
0, 0, root_info->bounds.width, root_info->bounds.height));
// Create a panel and drag it to the left so it's attached to the left
// dock. The workarea property should leave room on the left side of the
// screen for the dock.
Panel* panel = CreatePanel(200, 20, 400);
SendPanelDraggedMessage(panel, 0, 0);
SendPanelDragCompleteMessage(panel);
EXPECT_TRUE(
TestIntArrayProperty(
root_xid, workarea_atom, 4,
PanelManager::kPanelDockWidth, 0,
root_info->bounds.width - PanelManager::kPanelDockWidth,
root_info->bounds.height));
// Now dock it on the right.
SendPanelDraggedMessage(panel, root_info->bounds.width - 1, 0);
SendPanelDragCompleteMessage(panel);
EXPECT_TRUE(
TestIntArrayProperty(
root_xid, workarea_atom, 4,
0, 0,
root_info->bounds.width - PanelManager::kPanelDockWidth,
root_info->bounds.height));
// After the screen gets resized, the dock should still be taken into
// account.
root_info->bounds.width += 20;
root_info->bounds.height += 10;
XEvent event;
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(
TestIntArrayProperty(
root_xid, workarea_atom, 4,
0, 0,
root_info->bounds.width - PanelManager::kPanelDockWidth,
root_info->bounds.height));
}
// Test that the _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING properties
// on the root window get updated correctly.
TEST_F(WindowManagerTest, ClientListProperties) {
XWindow root_xid = xconn_->GetRootWindow();
XAtom list_atom = None, stacking_atom = None;
ASSERT_TRUE(xconn_->GetAtom("_NET_CLIENT_LIST", &list_atom));
ASSERT_TRUE(xconn_->GetAtom("_NET_CLIENT_LIST_STACKING", &stacking_atom));
// Both properties should be unset when there aren't any client windows.
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 0));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 0));
// Create and map a regular window.
XWindow xid = CreateSimpleWindow();
SendInitialEventsForWindow(xid);
SendConfigureNotifyEvent(xid);
// Both properties should contain just this window.
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 1, xid));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 1, xid));
// Create and map an override-redirect window.
XWindow override_redirect_xid =
xconn_->CreateWindow(
root_xid, // parent
Rect(0, 0, 200, 200),
true, // override_redirect
false, // input_only
0, 0); // event mask, visual
SendInitialEventsForWindow(override_redirect_xid);
// The override-redirect window shouldn't be included.
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 1, xid));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 1, xid));
// Create and map a second regular window.
XWindow xid2 = CreateSimpleWindow();
SendInitialEventsForWindow(xid2);
SendConfigureNotifyEvent(xid);
SendConfigureNotifyEvent(xid2);
// The second window should appear after the first in _NET_CLIENT_LIST,
// since it was mapped after it, and after the first in
// _NET_CLIENT_LIST_STACKING, since it's stacked above it (new windows
// get stacked above their siblings).
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 2, xid, xid2));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 2, xid, xid2));
// Raise the override-redirect window above the others.
ASSERT_TRUE(xconn_->RaiseWindow(override_redirect_xid));
SendConfigureNotifyEvent(override_redirect_xid);
// The properties should be unchanged.
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 2, xid, xid2));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 2, xid, xid2));
// Raise the first window on top of the second window.
ASSERT_TRUE(xconn_->StackWindow(xid, xid2, true));
SendConfigureNotifyEvent(xid);
// The list property should be unchanged, but the second window should
// appear first in the stacking property since it's now on the bottom.
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 2, xid, xid2));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 2, xid2, xid));
// Destroy the first window.
ASSERT_TRUE(xconn_->DestroyWindow(xid));
XEvent event;
xconn_->InitUnmapEvent(&event, xid);
wm_->HandleEvent(&event);
xconn_->InitDestroyWindowEvent(&event, xid);
wm_->HandleEvent(&event);
// Both properties should just contain the second window now.
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 1, xid2));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 1, xid2));
// Tell the window manager that the second window was reparented away.
XReparentEvent* reparent_event = &(event.xreparent);
memset(reparent_event, 0, sizeof(*reparent_event));
reparent_event->type = ReparentNotify;
reparent_event->window = xid2;
reparent_event->parent = 324324; // arbitrary number
wm_->HandleEvent(&event);
// The properties should be unset.
EXPECT_TRUE(TestIntArrayProperty(root_xid, list_atom, 0));
EXPECT_TRUE(TestIntArrayProperty(root_xid, stacking_atom, 0));
}
TEST_F(WindowManagerTest, WmIpcVersion) {
// BasicWindowManagerTest::SetUp() sends a WM_NOTIFY_IPC_VERSION message
// automatically, since most tests want something reasonable there.
// Create a new WindowManager object to work around this.
CreateAndInitNewWm();
// We should assume version 1 if we haven't received a message from Chrome.
EXPECT_EQ(1, wm_->wm_ipc_version());
// Now send the WM a message telling it that Chrome is using version 3.
WmIpc::Message msg(chromeos::WM_IPC_MESSAGE_WM_NOTIFY_IPC_VERSION);
msg.set_param(0, 3);
SendWmIpcMessage(msg);
EXPECT_EQ(3, wm_->wm_ipc_version());
}
// Test that all windows get redirected when they're created.
TEST_F(WindowManagerTest, RedirectWindows) {
// First, create a window that's already mapped when the window manager is
// started.
wm_.reset(NULL);
xconn_.reset(new MockXConnection);
SetLoggedInState(true);
RegisterCommonKeySyms();
event_loop_.reset(new EventLoop);
compositor_.reset(new MockCompositor(xconn_.get()));
XWindow existing_xid = CreateSimpleWindow();
MockXConnection::WindowInfo* existing_info =
xconn_->GetWindowInfoOrDie(existing_xid);
xconn_->MapWindow(existing_xid);
EXPECT_FALSE(existing_info->redirected);
CreateAndInitNewWm();
// Check that the window manager redirected it.
EXPECT_TRUE(existing_info->redirected);
Window* existing_win = wm_->GetWindowOrDie(existing_xid);
MockCompositor::TexturePixmapActor* existing_mock_actor =
GetMockActorForWindow(existing_win);
EXPECT_TRUE(existing_mock_actor->pixmap() != 0);
// Now, create a new window, but don't map it yet. The window manager
// should've already told the X server to automatically redirect toplevel
// windows.
XWindow xid = CreateSimpleWindow();
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
EXPECT_TRUE(info->redirected);
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
Window* win = wm_->GetWindowOrDie(xid);
MockCompositor::TexturePixmapActor* mock_actor = GetMockActorForWindow(win);
EXPECT_EQ(0, mock_actor->pixmap());
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(win->mapped());
EXPECT_TRUE(existing_mock_actor->pixmap() != 0);
// There won't be a MapRequest event for override-redirect windows, but they
// should still get redirected automatically.
XWindow override_redirect_xid = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(10, 20, 30, 40),
true, // override redirect
false, // input only
0, 0); // event mask, visual
MockXConnection::WindowInfo* override_redirect_info =
xconn_->GetWindowInfoOrDie(override_redirect_xid);
EXPECT_TRUE(override_redirect_info->redirected);
xconn_->MapWindow(override_redirect_xid);
ASSERT_TRUE(override_redirect_info->mapped);
// Send a CreateNotify event to the window manager.
xconn_->InitCreateWindowEvent(&event, override_redirect_xid);
wm_->HandleEvent(&event);
Window* override_redirect_win = wm_->GetWindowOrDie(override_redirect_xid);
MockCompositor::TexturePixmapActor* override_redirect_mock_actor =
GetMockActorForWindow(override_redirect_win);
EXPECT_EQ(0, override_redirect_mock_actor->pixmap());
EXPECT_FALSE(override_redirect_win->mapped());
xconn_->InitMapEvent(&event, override_redirect_xid);
wm_->HandleEvent(&event);
EXPECT_TRUE(override_redirect_win->mapped());
EXPECT_TRUE(override_redirect_mock_actor->pixmap() != 0);
}
// This tests against a bug where the window manager would fail to handle
// existing panel windows at startup -- see http://crosbug.com/1591.
TEST_F(WindowManagerTest, KeepPanelsAfterRestart) {
// Create a panel and check that the window manager handles it.
Panel* panel = CreatePanel(200, 20, 400);
const XWindow titlebar_xid = panel->titlebar_xid();
const XWindow content_xid = panel->content_xid();
const Window* win = wm_->GetWindow(content_xid);
ASSERT_TRUE(win != NULL);
EXPECT_EQ(panel, wm_->panel_manager_->panel_bar_->GetPanelByWindow(*win));
wm_.reset();
// XConnection::GetChildWindows() returns windows in bottom-to-top order.
// We want to make sure that the window manager is able to deal with
// seeing the content window show up before the titlebar window when it
// asks for all of the existing windows at startup, so stack the content
// window beneath the titlebar window.
ASSERT_TRUE(xconn_->StackWindow(content_xid, titlebar_xid, false));
// Call GetChildWindows() to make sure that the windows are stacked as we
// intended.
vector<XWindow> windows;
ASSERT_TRUE(xconn_->GetChildWindows(xconn_->GetRootWindow(), &windows));
vector<XWindow>::iterator titlebar_it =
find(windows.begin(), windows.end(), titlebar_xid);
ASSERT_TRUE(titlebar_it != windows.end());
vector<XWindow>::iterator content_it =
find(windows.begin(), windows.end(), content_xid);
ASSERT_TRUE(content_it != windows.end());
ASSERT_TRUE(content_it < titlebar_it);
// Now create and initialize a new window manager and check that it
// creates a new Panel object.
CreateAndInitNewWm();
win = wm_->GetWindow(content_xid);
ASSERT_TRUE(win != NULL);
ASSERT_TRUE(wm_->panel_manager_->panel_bar_->GetPanelByWindow(*win) != NULL);
}
// Makes sure the _CHROME_LOGGED_IN property is interpreted correctly.
TEST_F(WindowManagerTest, LoggedIn) {
EXPECT_TRUE(wm_->logged_in());
// When the _CHROME_LOGGED_IN property doesn't exist, the window manager
// should assume that we're not logged in.
XAtom logged_in_xatom = xconn_->GetAtomOrDie("_CHROME_LOGGED_IN");
xconn_->DeletePropertyIfExists(xconn_->GetRootWindow(), logged_in_xatom);
CreateAndInitNewWm();
EXPECT_FALSE(wm_->logged_in());
// Ditto for when it exists but is set to 0.
SetLoggedInState(false);
CreateAndInitNewWm();
EXPECT_FALSE(wm_->logged_in());
// Check that we handle property changes too.
TestEventConsumer ec;
wm_->event_consumers_.insert(&ec);
SetLoggedInState(true);
EXPECT_TRUE(wm_->logged_in());
EXPECT_EQ(1, ec.num_logged_in_state_changes());
// We should ignore logged-in to not-logged-in transitions.
ec.reset_stats();
SetLoggedInState(false);
EXPECT_TRUE(wm_->logged_in());
EXPECT_EQ(0, ec.num_logged_in_state_changes());
}
// Test that the window manager refreshes the keyboard map when it gets a
// MappingNotify event.
TEST_F(WindowManagerTest, HandleMappingNotify) {
// Check that a grab has been installed for an arbitrary key binding
// (Ctrl-F5).
EXPECT_EQ(0, xconn_->num_keymap_refreshes());
const KeySym keysym = XK_F5;
const uint32_t modifiers = KeyBindings::kControlMask;
const KeyCode old_keycode = xconn_->GetKeyCodeFromKeySym(keysym);
EXPECT_TRUE(xconn_->KeyIsGrabbed(old_keycode, modifiers));
// Now remap the F5 key and give the window manager a MappingNotify event.
const KeyCode new_keycode = 255;
EXPECT_FALSE(xconn_->KeyIsGrabbed(new_keycode, modifiers));
xconn_->RemoveKeyMapping(old_keycode, keysym);
xconn_->AddKeyMapping(new_keycode, keysym);
XEvent event;
XMappingEvent* mapping_event = &(event.xmapping);
memset(mapping_event, 0, sizeof(mapping_event));
mapping_event->type = MappingNotify;
mapping_event->request = MappingKeyboard;
mapping_event->first_keycode = 1;
mapping_event->count = 6;
wm_->HandleEvent(&event);
// The XConnection should've been told to refresh its keymap, and the
// keyboard grab should be updated (there are more-extensive tests of the
// latter behavior in KeyBindingsTest).
EXPECT_EQ(1, xconn_->num_keymap_refreshes());
EXPECT_TRUE(xconn_->KeyIsGrabbed(new_keycode, modifiers));
EXPECT_FALSE(xconn_->KeyIsGrabbed(old_keycode, modifiers));
}
// Check that the window manager tells the Window class to tell the
// compositor to discard the pixmap for a window when the window is resized
// or remapped. See http://crosbug.com/3159.
TEST_F(WindowManagerTest, FetchNewPixmap) {
XWindow xid = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(10, 20, 30, 40),
true, // override redirect
false, // input only
0, 0); // event mask, visual
MockXConnection::WindowInfo* info = xconn_->GetWindowInfoOrDie(xid);
xconn_->MapWindow(xid);
ASSERT_TRUE(info->mapped);
SendInitialEventsForWindow(xid);
Window* win = wm_->GetWindowOrDie(xid);
MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(win);
EXPECT_TRUE(actor->pixmap() != 0);
MockXConnection::PixmapInfo* pixmap_info =
xconn_->GetPixmapInfo(actor->pixmap());
ASSERT_TRUE(pixmap_info != NULL);
EXPECT_EQ(info->bounds.width, pixmap_info->size.width);
EXPECT_EQ(info->bounds.height, pixmap_info->size.height);
// Check that the pixmap gets reset when the window gets resized.
XID prev_pixmap = actor->pixmap();
ASSERT_TRUE(xconn_->ResizeWindow(
xid,
Size(info->bounds.width + 10, info->bounds.height)));
XEvent event;
xconn_->InitConfigureNotifyEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_NE(prev_pixmap, actor->pixmap());
pixmap_info = xconn_->GetPixmapInfo(actor->pixmap());
ASSERT_TRUE(pixmap_info != NULL);
EXPECT_EQ(info->bounds.width, pixmap_info->size.width);
EXPECT_EQ(info->bounds.height, pixmap_info->size.height);
// We should reset it when the window is remapped, too (but we should
// continue using the old pixmap until we actually see the window get
// mapped again).
prev_pixmap = actor->pixmap();
xconn_->InitUnmapEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_EQ(prev_pixmap, actor->pixmap());
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_NE(prev_pixmap, actor->pixmap());
pixmap_info = xconn_->GetPixmapInfo(actor->pixmap());
ASSERT_TRUE(pixmap_info != NULL);
EXPECT_EQ(info->bounds.width, pixmap_info->size.width);
EXPECT_EQ(info->bounds.height, pixmap_info->size.height);
}
// Test that we switch log files after the user logs in.
TEST_F(WindowManagerTest, StartNewLogAfterLogin) {
wm_.reset(NULL);
ScopedTempDirectory logged_in_dir;
AutoReset<string> logged_in_flag_resetter(
&FLAGS_logged_in_log_dir, logged_in_dir.path().value());
ScopedTempDirectory logged_out_dir;
AutoReset<string> logged_out_flag_resetter(
&FLAGS_logged_out_log_dir, logged_out_dir.path().value());
// Make sure that logging is turned on, and pretend like we just started
// while not logged in.
SetLoggedInState(false);
CreateNewWm();
wm_->set_initialize_logging(true);
CHECK(wm_->Init());
ASSERT_FALSE(wm_->logged_in());
// The logged-in directory should be empty, but the logged-out directory
// should contain data.
LOG(INFO) << "Pre-login log message";
EXPECT_EQ(static_cast<off_t>(0),
GetTotalFileSizeInDirectory(logged_in_dir.path()));
EXPECT_GT(GetTotalFileSizeInDirectory(logged_out_dir.path()),
static_cast<off_t>(0));
// After we log in and log a message, both directories should have data.
SetLoggedInState(true);
ASSERT_TRUE(wm_->logged_in());
LOG(INFO) << "Post-login log message";
off_t logged_in_size = GetTotalFileSizeInDirectory(logged_in_dir.path());
off_t logged_out_size = GetTotalFileSizeInDirectory(logged_out_dir.path());
EXPECT_GT(logged_in_size, static_cast<off_t>(0));
// Log another message and check that the logged-in directory increased in
// size but the logged-out one remained the same.
LOG(INFO) << "Another post-login log message";
EXPECT_GT(GetTotalFileSizeInDirectory(logged_in_dir.path()), logged_in_size);
EXPECT_EQ(logged_out_size,
GetTotalFileSizeInDirectory(logged_out_dir.path()));
}
// Check that we don't display drop shadows for most types of
// override-redirect windows.
TEST_F(WindowManagerTest, OverrideRedirectShadows) {
XAtom win_type_xatom = xconn_->GetAtomOrDie("_NET_WM_WINDOW_TYPE");
XAtom atom_xatom = xconn_->GetAtomOrDie("ATOM");
XAtom menu_xatom = xconn_->GetAtomOrDie("_NET_WM_WINDOW_TYPE_MENU");
XAtom popup_xatom = xconn_->GetAtomOrDie("_NET_WM_WINDOW_TYPE_POPUP_MENU");
// An override-redirect window with no _NET_WM_WINDOW_TYPE property
// shouldn't get a shadow.
const XWindow root = xconn_->GetRootWindow();
XWindow xid1 =
xconn_->CreateWindow(root, Rect(0, 0, 10, 10), true, false, 0, 0);
ASSERT_TRUE(xconn_->MapWindow(xid1));
SendInitialEventsForWindow(xid1);
Window* win1 = wm_->GetWindowOrDie(xid1);
EXPECT_TRUE(win1->shadow() == NULL);
// _NET_WM_WINDOW_TYPE_MENU (or several other menu-related types) should
// result in a shadow getting shown.
XWindow xid2 =
xconn_->CreateWindow(root, Rect(0, 0, 10, 10), true, false, 0, 0);
xconn_->SetIntProperty(xid2, win_type_xatom, atom_xatom, menu_xatom);
ASSERT_TRUE(xconn_->MapWindow(xid2));
SendInitialEventsForWindow(xid2);
ASSERT_TRUE(wm_->GetWindowOrDie(xid2)->shadow() != NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(xid2)->shadow()->is_shown());
XAtom normal_xatom = 0;
ASSERT_TRUE(xconn_->GetAtom("_NET_WM_WINDOW_TYPE_NORMAL", &normal_xatom));
// A non-menu type should result in no shadow getting shown...
XWindow xid3 =
xconn_->CreateWindow(root, Rect(0, 0, 10, 10), true, false, 0, 0);
xconn_->SetIntProperty(xid3, win_type_xatom, atom_xatom, normal_xatom);
ASSERT_TRUE(xconn_->MapWindow(xid3));
SendInitialEventsForWindow(xid3);
EXPECT_TRUE(wm_->GetWindowOrDie(xid3)->shadow() == NULL);
// ...unless there's another menu type in the property.
XWindow xid4 =
xconn_->CreateWindow(root, Rect(0, 0, 10, 10), true, false, 0, 0);
vector<int> values;
values.push_back(normal_xatom);
values.push_back(popup_xatom);
xconn_->SetIntArrayProperty(xid4, win_type_xatom, atom_xatom, values);
ASSERT_TRUE(xconn_->MapWindow(xid4));
SendInitialEventsForWindow(xid4);
ASSERT_TRUE(wm_->GetWindowOrDie(xid4)->shadow() != NULL);
EXPECT_TRUE(wm_->GetWindowOrDie(xid4)->shadow()->is_shown());
// We should avoid showing shadows behind RGBA menus.
XWindow rgba_xid =
xconn_->CreateWindow(root, Rect(0, 0, 10, 10), true, false, 0, 0);
xconn_->GetWindowInfoOrDie(rgba_xid)->depth = 32;
xconn_->SetIntProperty(rgba_xid, win_type_xatom, atom_xatom, menu_xatom);
ASSERT_TRUE(xconn_->MapWindow(rgba_xid));
SendInitialEventsForWindow(rgba_xid);
Window* rgba_win = wm_->GetWindowOrDie(rgba_xid);
EXPECT_TRUE(rgba_win->shadow() == NULL);
}
// Check that we try to guess when is a video is playing by looking at the
// rate and size of damage events, and that we set the _CHROME_VIDEO_TIME
// property on the root window accordingly.
TEST_F(WindowManagerTest, VideoTimeProperty) {
SetMonotonicTimeForTest(CreateTimeTicksFromMs(2000000));
const time_t start_time = 1000;
SetCurrentTimeForTest(start_time, 0);
XWindow xid = CreateSimpleWindow();
SendInitialEventsForWindow(xid);
const XAtom atom = xconn_->GetAtomOrDie("_CHROME_VIDEO_TIME");
int video_time = 0;
EXPECT_FALSE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
// First send damage events at a high-enough framerate, but for regions
// that are too small to trigger the code.
XEvent event;
xconn_->InitDamageNotifyEvent(
&event, xid,
Rect(0, 0, Window::kVideoMinWidth - 1, Window::kVideoMinHeight - 1));
for (int i = 0; i < Window::kVideoMinFramerate + 3; ++i)
wm_->HandleEvent(&event);
EXPECT_FALSE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
// Now send events with larger regions, but send one fewer than the
// required number of frames.
xconn_->InitDamageNotifyEvent(
&event, xid,
Rect(0, 0, Window::kVideoMinWidth, Window::kVideoMinHeight));
for (int i = 0; i < Window::kVideoMinFramerate - 1; ++i)
wm_->HandleEvent(&event);
EXPECT_FALSE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
// After one more frame, we should set the property.
wm_->HandleEvent(&event);
EXPECT_TRUE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
EXPECT_EQ(start_time, video_time);
// Send a bunch more frames the next second. We should leave the
// property alone, since not enough time has passed for us to update it.
ASSERT_GT(WindowManager::kVideoTimePropertyUpdateSec, 1);
SetMonotonicTimeForTest(
GetMonotonicTime() + TimeDelta::FromMilliseconds(1000));
SetCurrentTimeForTest(start_time + 1, 0);
for (int i = 0; i < Window::kVideoMinFramerate + 10; ++i)
wm_->HandleEvent(&event);
EXPECT_TRUE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
EXPECT_EQ(start_time, video_time);
// Wait the minimum required time to update the property and send more
// frames, but spread them out across two seconds so that the per-second
// rate isn't high enough. We should still leave the property alone.
SetMonotonicTimeForTest(
GetMonotonicTime() +
TimeDelta::FromSeconds(WindowManager::kVideoTimePropertyUpdateSec));
SetCurrentTimeForTest(
start_time + WindowManager::kVideoTimePropertyUpdateSec, 0);
for (int i = 0; i < Window::kVideoMinFramerate - 5; ++i)
wm_->HandleEvent(&event);
SetMonotonicTimeForTest(
GetMonotonicTime() +
TimeDelta::FromSeconds(WindowManager::kVideoTimePropertyUpdateSec));
SetCurrentTimeForTest(
start_time + WindowManager::kVideoTimePropertyUpdateSec + 1, 0);
for (int i = 0; i < Window::kVideoMinFramerate - 5; ++i)
wm_->HandleEvent(&event);
EXPECT_TRUE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
EXPECT_EQ(start_time, video_time);
// Now send some more frames and check that the property is updated.
for (int i = 0; i < 5; ++i)
wm_->HandleEvent(&event);
EXPECT_TRUE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
EXPECT_EQ(start_time + WindowManager::kVideoTimePropertyUpdateSec + 1,
video_time);
// Move the monotonic clock forward but the system clock backward, send some
// more frames, and check that the property is updated (see
// http://crosbug.com/4940).
SetMonotonicTimeForTest(
GetMonotonicTime() +
TimeDelta::FromSeconds(2 * WindowManager::kVideoTimePropertyUpdateSec));
SetCurrentTimeForTest(start_time - 5, 0);
for (int i = 0; i < Window::kVideoMinFramerate + 10; ++i)
wm_->HandleEvent(&event);
EXPECT_TRUE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
EXPECT_EQ(start_time - 5, video_time);
// Move the window offscreen and check that we no longer update the property
// in response to damage events for it.
Window* win = wm_->GetWindowOrDie(xid);
win->Move(Point(wm_->width(), wm_->height()), 0);
ASSERT_TRUE(WindowIsOffscreen(xid));
SetMonotonicTimeForTest(
GetMonotonicTime() +
TimeDelta::FromSeconds(2 * WindowManager::kVideoTimePropertyUpdateSec));
SetCurrentTimeForTest(start_time + 5, 0);
for (int i = 0; i < 30; ++i)
wm_->HandleEvent(&event);
EXPECT_TRUE(xconn_->GetIntProperty(xconn_->GetRootWindow(),
atom, &video_time));
EXPECT_EQ(start_time - 5, video_time);
}
// Test the unredirect fullscreen window optimization. Check the windows
// get properly directed/unredirected when the fullscreen actor changes.
TEST_F(WindowManagerTest, HandleTopFullscreenActorChange) {
AutoReset<bool> unredirect_flag_resetter(
&FLAGS_unredirect_fullscreen_window, true);
XWindow xwin1 = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(0, 0, wm_->width(), wm_->height()),
true, // override redirect
false, // input only
0, 0); // event mask, visual
XWindow xwin2 = xconn_->CreateWindow(
xconn_->GetRootWindow(),
Rect(0, 0, wm_->width(), wm_->height()),
true, // override redirect
false, // input only
0, 0); // event mask, visual
MockXConnection::WindowInfo* info1 = xconn_->GetWindowInfoOrDie(xwin1);
MockXConnection::WindowInfo* info2 = xconn_->GetWindowInfoOrDie(xwin2);
SendInitialEventsForWindow(xwin1);
SendInitialEventsForWindow(xwin2);
MockCompositor::TexturePixmapActor* actor1 = GetMockActorForWindow(
wm_->GetWindowOrDie(xwin1));
MockCompositor::TexturePixmapActor* actor2 = GetMockActorForWindow(
wm_->GetWindowOrDie(xwin2));
// Move and scale the two windows to fit the screen.
xconn_->ConfigureWindow(xwin1, Rect(0, 0, wm_->width(), wm_->height()));
xconn_->ConfigureWindow(xwin2, Rect(0, 0, wm_->width(), wm_->height()));
XEvent event;
xconn_->InitConfigureNotifyEvent(&event, xwin1);
wm_->HandleEvent(&event);
xconn_->InitConfigureNotifyEvent(&event, xwin2);
wm_->HandleEvent(&event);
// Set up overlay regions for comparison.
MockXConnection::WindowInfo* overlay_info =
xconn_->GetWindowInfoOrDie(wm_->overlay_xid_);
scoped_ptr<ByteMap> expected_overlay(
new ByteMap(overlay_info->bounds.size()));
scoped_ptr<ByteMap> actual_overlay(
new ByteMap(overlay_info->bounds.size()));
// Make sure no window is unredirected.
EXPECT_EQ(static_cast<XWindow>(0), wm_->unredirected_fullscreen_xid_);
EXPECT_TRUE(info1->redirected);
EXPECT_TRUE(info2->redirected);
expected_overlay->Clear(0xff);
xconn_->GetWindowBoundingRegion(wm_->overlay_xid_, actual_overlay.get());
EXPECT_TRUE(*expected_overlay.get() == *actual_overlay.get());
// Test transition from no fullscreen actor to having a fullscreen actor.
wm_->HandleTopFullscreenActorChange(actor1);
EXPECT_EQ(xwin1, wm_->unredirected_fullscreen_xid_);
// We would expect this method to be posted to the event loop via
// HandleTopFullscreenActorChange(), but it is called manually here since
// the event loop isn't started in the tests.
wm_->DisableCompositing();
EXPECT_FALSE(info1->redirected);
EXPECT_TRUE(info2->redirected);
expected_overlay->Clear(0);
xconn_->GetWindowBoundingRegion(wm_->overlay_xid_, actual_overlay.get());
EXPECT_TRUE(*expected_overlay.get() == *actual_overlay.get());
EXPECT_FALSE(compositor_->should_draw_frame());
// Test change from one to another top fullscreen actor.
wm_->HandleTopFullscreenActorChange(actor2);
EXPECT_EQ(xwin2, wm_->unredirected_fullscreen_xid_);
wm_->DisableCompositing();
EXPECT_TRUE(info1->redirected);
EXPECT_FALSE(info2->redirected);
xconn_->GetWindowBoundingRegion(wm_->overlay_xid_, actual_overlay.get());
EXPECT_TRUE(*expected_overlay.get() == *actual_overlay.get());
EXPECT_FALSE(compositor_->should_draw_frame());
// Test transition from having fullscreen actor to not.
wm_->HandleTopFullscreenActorChange(NULL);
EXPECT_EQ(static_cast<XWindow>(0), wm_->unredirected_fullscreen_xid_);
EXPECT_TRUE(info1->redirected);
EXPECT_TRUE(info2->redirected);
expected_overlay->Clear(0xff);
xconn_->GetWindowBoundingRegion(wm_->overlay_xid_, actual_overlay.get());
EXPECT_TRUE(*expected_overlay.get() == *actual_overlay.get());
EXPECT_TRUE(compositor_->should_draw_frame());
}
// Check that WindowManager passes ownership of destroyed windows to
// EventConsumers who asked for them.
TEST_F(WindowManagerTest, DestroyedWindows) {
TestEventConsumer ec;
XWindow xid = CreateSimpleWindow();
wm_->RegisterEventConsumerForDestroyedWindow(xid, &ec);
SendInitialEventsForWindow(xid);
Window* win = wm_->GetWindowOrDie(xid);
win->SetShadowType(Shadow::TYPE_RECTANGULAR);
Compositor::TexturePixmapActor* actor = win->actor();
const Shadow* shadow = win->shadow();
ASSERT_TRUE(shadow != NULL);
XEvent event;
xconn_->InitUnmapEvent(&event, xid);
wm_->HandleEvent(&event);
xconn_->InitDestroyWindowEvent(&event, xid);
wm_->HandleEvent(&event);
// After we destroy the X window, WindowManager should no longer have a
// Window object tracking it, but our EventConsumer should've received a
// DestroyedWindow object containing the original actor and shadow.
EXPECT_TRUE(wm_->GetWindow(xid) == NULL);
ASSERT_EQ(static_cast<size_t>(1), ec.destroyed_windows().size());
DestroyedWindow* destroyed_win = ec.destroyed_windows().begin()->get();
EXPECT_EQ(actor, destroyed_win->actor());
EXPECT_EQ(shadow, destroyed_win->shadow());
}
// Test that we defer fetching a window's initial pixmap until the client
// tells us that it's been painted, and that we notify EventConsumers when
// we've fetched the pixmap.
TEST_F(WindowManagerTest, NotifyAboutInitialPixmap) {
TestEventConsumer ec;
// Create a window that doesn't support the _NET_WM_SYNC_REQUEST
// protocol. We should fetch its pixmap as soon as it gets mapped and also
// notify the event consumer.
XWindow xid = CreateSimpleWindow();
wm_->RegisterEventConsumerForWindowEvents(xid, &ec);
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
xconn_->InitMapRequestEvent(&event, xid);
wm_->HandleEvent(&event);
ASSERT_TRUE(xconn_->GetWindowInfoOrDie(xid)->mapped);
EXPECT_TRUE(wm_->GetWindowOrDie(xid)->has_initial_pixmap());
xconn_->InitMapEvent(&event, xid);
wm_->HandleEvent(&event);
EXPECT_EQ(1, ec.num_fetched_pixmaps());
// Create a window that supports _NET_WM_SYNC_REQUEST.
// Window::has_initial_pixmap() should return false after it's mapped
// (since we should defer fetching the pixmap until the window says that
// it's painted it).
ec.reset_stats();
XWindow sync_xid = CreateSimpleWindow();
wm_->RegisterEventConsumerForWindowEvents(sync_xid, &ec);
ConfigureWindowForSyncRequestProtocol(sync_xid);
xconn_->InitCreateWindowEvent(&event, sync_xid);
wm_->HandleEvent(&event);
Window* sync_win = wm_->GetWindowOrDie(sync_xid);
xconn_->InitMapRequestEvent(&event, sync_xid);
wm_->HandleEvent(&event);
ASSERT_TRUE(xconn_->GetWindowInfoOrDie(sync_xid)->mapped);
xconn_->InitMapEvent(&event, sync_xid);
wm_->HandleEvent(&event);
EXPECT_EQ(0, ec.num_fetched_pixmaps());
EXPECT_FALSE(sync_win->has_initial_pixmap());
// Notify the window manager that the pixmap has been painted.
// has_initial_pixmap() should return true now, and our event consumer
// should be notified that the pixmap was received.
SendSyncRequestProtocolAlarm(sync_xid);
EXPECT_TRUE(sync_win->has_initial_pixmap());
EXPECT_EQ(1, ec.num_fetched_pixmaps());
// Resize the window and mimic the client syncing with the window manager
// again, and check that we notify the event consumer about the new pixmap.
sync_win->Resize(Size(600, 500), GRAVITY_NORTHWEST);
SendSyncRequestProtocolAlarm(sync_xid);
EXPECT_TRUE(sync_win->has_initial_pixmap());
EXPECT_EQ(2, ec.num_fetched_pixmaps());
}
// Test that we make a second attempt at loading the sync request counter
// ID if it only gets set after WM_PROTOCOLS has already told us that the
// protocol is supported. See comment #3 at http://crosbug.com/5846.
TEST_F(WindowManagerTest, HandleLateSyncRequestCounter) {
XWindow xid = CreateSimpleWindow();
AppendAtomToProperty(xid,
xconn_->GetAtomOrDie("WM_PROTOCOLS"),
xconn_->GetAtomOrDie("_NET_WM_SYNC_REQUEST"));
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
Window* win = wm_->GetWindowOrDie(xid);
EXPECT_EQ(0, win->wm_sync_request_alarm_);
ASSERT_TRUE(xconn_->SetIntProperty(
xid,
xconn_->GetAtomOrDie("_NET_WM_SYNC_REQUEST_COUNTER"), // atom
xconn_->GetAtomOrDie("CARDINAL"), // type
50)); // arbitrary counter ID
xconn_->InitPropertyNotifyEvent(
&event, xid, xconn_->GetAtomOrDie("_NET_WM_SYNC_REQUEST_COUNTER"));
wm_->HandleEvent(&event);
EXPECT_NE(0, win->wm_sync_request_alarm_);
}
// Check that we load color depths for newly-created windows.
TEST_F(WindowManagerTest, FetchDepth) {
XWindow rgb_xid = CreateSimpleWindow();
xconn_->GetWindowInfoOrDie(rgb_xid)->depth = 24;
SendInitialEventsForWindow(rgb_xid);
EXPECT_EQ(24, wm_->GetWindowOrDie(rgb_xid)->client_depth());
XWindow rgba_xid = CreateSimpleWindow();
xconn_->GetWindowInfoOrDie(rgba_xid)->depth = 32;
SendInitialEventsForWindow(rgba_xid);
EXPECT_EQ(32, wm_->GetWindowOrDie(rgba_xid)->client_depth());
}
// Test that we don't crash when a window is destroyed while a map request for
// it is in-flight to the window manager. See http://crosbug.com/12212.
TEST_F(WindowManagerTest, WindowDestroyedWhileBeingMapped) {
// Create a window and let the window manager know about it.
XWindow xid = CreateSimpleWindow();
XEvent event;
xconn_->InitCreateWindowEvent(&event, xid);
wm_->HandleEvent(&event);
// Now destroy the window and *then* send a map request to the window manager
// about it. In reality, the client would send the request first and destroy
// the window while the request was in-flight to the WM... which seems dumb,
// but that apparently doesn't keep clients from doing it.
xconn_->InitMapRequestEvent(&event, xid);
xconn_->DestroyWindow(xid);
wm_->HandleEvent(&event);
// Finally, let the WM know that the window was destroyed. Note that we don't
// send an unmap notify event first, since the window was already destroyed by
// the time that the WM tried to map it. This tests that the WM doesn't make
// an assumption that its map request succeeded.
xconn_->InitDestroyWindowEvent(&event, xid);
wm_->HandleEvent(&event);
}
// Test that the IncrementCompositingRequests() method, used to force
// compositing, works as expected.
TEST_F(WindowManagerTest, ForceCompositing) {
AutoReset<bool> unredirect_flag_resetter(
&FLAGS_unredirect_fullscreen_window, true);
// Create a window.
XWindow xid = CreateSimpleWindow();
ConfigureWindowForSyncRequestProtocol(xid);
SendInitialEventsForWindow(xid);
SendSyncRequestProtocolAlarm(xid);
Window* win = wm_->GetWindowOrDie(xid);
MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(win);
// Resize it to cover the whole screen.
win->Resize(wm_->root_size(), GRAVITY_NORTHWEST);
SendSyncRequestProtocolAlarm(xid);
// Tell the WM that the window is covering the screen and check that we
// disable compositing.
wm_->HandleTopFullscreenActorChange(actor);
EXPECT_EQ(xid, wm_->unredirected_fullscreen_xid_);
EXPECT_TRUE(wm_->disable_compositing_task_is_pending_);
wm_->DisableCompositing();
EXPECT_FALSE(compositor_->should_draw_frame());
// Now force compositing back on and check that we redraw the scene.
const int kPrevForcedDraws = compositor_->num_forced_draws();
wm_->IncrementCompositingRequests();
EXPECT_EQ(kPrevForcedDraws + 1, compositor_->num_forced_draws());
wm_->HandleTopFullscreenActorChange(actor);
EXPECT_EQ(static_cast<XWindow>(0), wm_->unredirected_fullscreen_xid_);
EXPECT_TRUE(compositor_->should_draw_frame());
EXPECT_FALSE(wm_->disable_compositing_task_is_pending_);
// Resize the window and remove the compositing request. Since we're waiting
// for the window to be repainted after the resize, we should continue
// compositing.
win->Resize(Size(wm_->width() / 2, wm_->height() / 2), GRAVITY_NORTHWEST);
wm_->DecrementCompositingRequests();
wm_->HandleTopFullscreenActorChange(actor);
EXPECT_EQ(static_cast<XWindow>(0), wm_->unredirected_fullscreen_xid_);
EXPECT_TRUE(compositor_->should_draw_frame());
EXPECT_FALSE(wm_->disable_compositing_task_is_pending_);
// After the window is repainted, we should still be compositing since the
// window is no longer covering the entire screen.
SendSyncRequestProtocolAlarm(xid);
wm_->HandleTopFullscreenActorChange(NULL);
EXPECT_EQ(static_cast<XWindow>(0), wm_->unredirected_fullscreen_xid_);
EXPECT_TRUE(compositor_->should_draw_frame());
EXPECT_FALSE(wm_->disable_compositing_task_is_pending_);
}
// Test that we don't crash if we get a DestroyNotify event for a still-mapped
// window. See http://crosbug.com/13792.
TEST_F(WindowManagerTest, MissingUnmapNotify) {
// Map a window.
XWindow xid = CreateSimpleWindow();
SendInitialEventsForWindow(xid);
// Send notification about the window's destruction without saying anything
// about it getting unmapped first.
XEvent event;
xconn_->InitDestroyWindowEvent(&event, xid);
wm_->HandleEvent(&event);
// Map a second window (in the bug linked above, this appeared to make
// FocusManager access |xid|'s now-destroyed Window object).
XWindow xid2 = CreateSimpleWindow();
SendInitialEventsForWindow(xid2);
}
// Check that we don't mask out any parts of the compositing overlay window when
// the screen size gets increased while we're compositing. See
// http://crosbug.com/14951.
TEST_F(WindowManagerTest, ResizeScreenWhileCompositing) {
AutoReset<bool> unredirect_flag_resetter(
&FLAGS_unredirect_fullscreen_window, true);
// Create a fullscreen window and check that we disable compositing.
XWindow xid = CreateSimpleWindow();
SendInitialEventsForWindow(xid);
Window* win = wm_->GetWindowOrDie(xid);
win->Resize(wm_->root_size(), GRAVITY_NORTHWEST);
MockCompositor::TexturePixmapActor* actor = GetMockActorForWindow(win);
wm_->HandleTopFullscreenActorChange(actor);
ASSERT_EQ(xid, wm_->unredirected_fullscreen_xid_);
ASSERT_TRUE(wm_->disable_compositing_task_is_pending_);
wm_->DisableCompositing();
// Now force compositing back on. Previously, this would've had the effect of
// making us use the shape extension to make the bounds of the compositing
// overlay window match the screen size.
wm_->IncrementCompositingRequests();
wm_->HandleTopFullscreenActorChange(actor);
ASSERT_EQ(0, wm_->unredirected_fullscreen_xid_);
XWindow root_xid = xconn_->GetRootWindow();
MockXConnection::WindowInfo* root_info = xconn_->GetWindowInfoOrDie(root_xid);
XWindow overlay_xid = xconn_->GetCompositingOverlayWindow(root_xid);
MockXConnection::WindowInfo* overlay_info =
xconn_->GetWindowInfoOrDie(overlay_xid);
// Tell the window manager that the screen has gotten bigger.
Size kNewSize(root_info->bounds.width * 2, root_info->bounds.height * 2);
root_info->bounds.resize(kNewSize, GRAVITY_NORTHWEST);
overlay_info->bounds.resize(kNewSize, GRAVITY_NORTHWEST);
XEvent event;
xconn_->InitConfigureNotifyEvent(&event, root_xid);
wm_->HandleEvent(&event);
// Check that we're not masking any of the overlay window's new area.
scoped_ptr<ByteMap> expected_overlay(
new ByteMap(overlay_info->bounds.size()));
scoped_ptr<ByteMap> actual_overlay(
new ByteMap(overlay_info->bounds.size()));
expected_overlay->Clear(0xff);
xconn_->GetWindowBoundingRegion(overlay_xid, actual_overlay.get());
EXPECT_TRUE(*expected_overlay.get() == *actual_overlay.get());
}
} // namespace window_manager
int main(int argc, char** argv) {
return window_manager::InitAndRunTests(&argc, argv, &FLAGS_logtostderr);
}