| Index: ui/base/x/x11_window_cache_unittest.cc
|
| diff --git a/ui/base/x/x11_window_cache_unittest.cc b/ui/base/x/x11_window_cache_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4044f6255c6ca2053a5b8dc1d9909112db56d792
|
| --- /dev/null
|
| +++ b/ui/base/x/x11_window_cache_unittest.cc
|
| @@ -0,0 +1,551 @@
|
| +// Copyright 2016 The Chromium 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 "ui/base/x/x11_window_cache.h"
|
| +
|
| +#include <poll.h>
|
| +#include <X11/Xlib.h>
|
| +#include <X11/Xatom.h>
|
| +
|
| +// These macros defined by Xlib conflict with gtest
|
| +#undef None
|
| +#undef Bool
|
| +
|
| +#include "base/macros.h"
|
| +#include "base/posix/eintr_wrapper.h"
|
| +#include "base/time/time.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "ui/events/platform/x11/x11_event_source.h"
|
| +#include "ui/gfx/x/x11_types.h"
|
| +
|
| +namespace ui {
|
| +
|
| +class XWindowCacheTest;
|
| +
|
| +class TestX11EventSourceDelegate : public X11EventSourceDelegate {
|
| + public:
|
| + TestX11EventSourceDelegate(XWindowCacheTest* cache_test)
|
| + : cache_(nullptr), cache_test_(cache_test) {}
|
| +
|
| + void SetCache(XWindowCache* cache) { cache_ = cache; }
|
| +
|
| + protected:
|
| + void ProcessXEvent(XEvent* xevent) override;
|
| +
|
| + private:
|
| + XWindowCache* cache_;
|
| + XWindowCacheTest* cache_test_;
|
| +};
|
| +
|
| +class TestX11EventSource : public X11EventSource {
|
| + public:
|
| + TestX11EventSource(X11EventSourceDelegate* delegate, XDisplay* display)
|
| + : X11EventSource(delegate, display) {}
|
| +
|
| + // Returns false iff there was a connection error or a timeout.
|
| + bool BlockUntilConnectionIsReadable() {
|
| + int conn_fd = ConnectionNumber(display_);
|
| +
|
| + struct pollfd rfd;
|
| + rfd.fd = conn_fd;
|
| + rfd.events = POLLIN;
|
| + rfd.revents = 0;
|
| +
|
| + static constexpr unsigned int timeout_ms = 3000;
|
| + base::TimeTicks deadline =
|
| + base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(timeout_ms);
|
| + HANDLE_EINTR(poll(
|
| + &rfd, 1,
|
| + std::max(0L, (deadline - base::TimeTicks::Now()).InMilliseconds())));
|
| + return rfd.revents & POLLIN;
|
| + }
|
| +
|
| + // Ensures we have the state of the entire window tree. May block. Returns
|
| + // false iff there was a connection error or a timeout.
|
| + bool BlockUntilTreeIsCached() {
|
| + while (!request_queue_.empty()) {
|
| + if (!HasNextReply()) {
|
| + if (!BlockUntilConnectionIsReadable())
|
| + return false;
|
| + }
|
| + DispatchXEvents();
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + // Ensures we have processed all events sent server-side before Synchronize
|
| + // was called client-side. We may be forced to wait for more events to come
|
| + // that are not yet in the queue. You most likely want to
|
| + // BlockUntilWindowTreeIsCached before and after calling Synchronize. Blocks.
|
| + // Returns false iff there was a connection error or a timeout.
|
| + void Synchronize() {
|
| + XSync(display_, False);
|
| + DispatchXEvents();
|
| + }
|
| +
|
| + size_t NumberOfQueuedRequests() { return request_queue_.size(); }
|
| +
|
| + private:
|
| +};
|
| +
|
| +class XWindowCacheTest : public testing::Test {
|
| + public:
|
| + XWindowCacheTest()
|
| + : display_(gfx::GetXDisplay()),
|
| + display_root_(DefaultRootWindow(display_)),
|
| + cache_root_(XCB_WINDOW_NONE),
|
| + testing_atom_(XInternAtom(display_, "CHROMIUM_TESTING_ATOM", False)),
|
| + delegate_(this),
|
| + event_source_(&delegate_, display_),
|
| + cache_(nullptr) {}
|
| + ~XWindowCacheTest() override {}
|
| +
|
| + void SetUp() override {
|
| + cache_root_ = CreateWindow(display_root_);
|
| + cache_ = new XWindowCache(display_, &event_source_, cache_root_);
|
| + delegate_.SetCache(cache_);
|
| + }
|
| +
|
| + void TearDown() override {
|
| + XDestroyWindow(display_, cache_root_);
|
| + cache_root_ = XCB_WINDOW_NONE;
|
| + EnsureMemoryReclaimed();
|
| + delete cache_;
|
| + cache_ = nullptr;
|
| + delegate_.SetCache(cache_);
|
| + }
|
| +
|
| + void EnsureMemoryReclaimed() {
|
| + Synchronize();
|
| + EXPECT_TRUE(cache_->windows_.empty());
|
| + EXPECT_EQ(0U, event_source_.NumberOfQueuedRequests());
|
| + }
|
| +
|
| + XID CreateWindow(XID parent) {
|
| + XSetWindowAttributes swa;
|
| + swa.override_redirect = True;
|
| + return XCreateWindow(display_, parent, 0, 0, 1, 1, 0, CopyFromParent,
|
| + InputOutput, CopyFromParent, CWOverrideRedirect, &swa);
|
| + }
|
| +
|
| + void ProcessEvent(XWindowCache* cache, XEvent* event) {
|
| + cache->ProcessEvent(event);
|
| + }
|
| +
|
| + bool BlockUntilTreeIsCached() {
|
| + return event_source_.BlockUntilTreeIsCached();
|
| + }
|
| +
|
| + void Synchronize() { event_source_.Synchronize(); }
|
| +
|
| + void Reset() { cache_->ResetCacheImpl(); }
|
| +
|
| + void SetProperty8(XID window, XID property, uint8_t value) {
|
| + XChangeProperty(display_, window, property, XA_CARDINAL, 8, PropModeReplace,
|
| + &value, 1);
|
| + }
|
| +
|
| + template <typename T>
|
| + void VerifyStackingOrder(const XWindowCache::Window* window,
|
| + const T& container) {
|
| + EXPECT_TRUE(window);
|
| + EXPECT_EQ(container.size(), window->GetChildren().size());
|
| + auto it = window->GetChildren().begin();
|
| + for (XID id : container) {
|
| + EXPECT_EQ(id, (*it)->id());
|
| + ++it;
|
| + }
|
| + }
|
| +
|
| + XDisplay* display_;
|
| + XID display_root_;
|
| + XID cache_root_;
|
| + XAtom testing_atom_;
|
| +
|
| + TestX11EventSourceDelegate delegate_;
|
| + TestX11EventSource event_source_;
|
| + XWindowCache* cache_;
|
| +
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(XWindowCacheTest);
|
| +};
|
| +
|
| +void TestX11EventSourceDelegate::ProcessXEvent(XEvent* xevent) {
|
| + if (cache_)
|
| + cache_test_->ProcessEvent(cache_, xevent);
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, BasicTest) {
|
| + XID child1_xid = CreateWindow(cache_root_);
|
| + XID child2_xid = CreateWindow(cache_root_);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + auto parent_window = cache_->GetWindow(cache_root_);
|
| +
|
| + EXPECT_TRUE(parent_window);
|
| + EXPECT_EQ(2U, parent_window->GetChildren().size());
|
| + auto it = parent_window->GetChildren().begin();
|
| + EXPECT_EQ(child2_xid, (*it)->id());
|
| + EXPECT_EQ(child1_xid, (*++it)->id());
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, NestingTest) {
|
| + XID child_xid = CreateWindow(cache_root_);
|
| + XID nested_child_xid = CreateWindow(child_xid);
|
| + SetProperty8(nested_child_xid, testing_atom_, 0x42);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + auto parent_window = cache_->GetWindow(cache_root_);
|
| + EXPECT_TRUE(parent_window);
|
| + EXPECT_EQ(1U, parent_window->GetChildren().size());
|
| +
|
| + auto child_window = *parent_window->GetChildren().begin();
|
| + EXPECT_TRUE(child_window);
|
| + EXPECT_EQ(1U, child_window->GetChildren().size());
|
| +
|
| + auto nested_child_window = *child_window->GetChildren().begin();
|
| + EXPECT_TRUE(nested_child_window);
|
| + EXPECT_EQ(0U, nested_child_window->GetChildren().size());
|
| +
|
| + auto prop = nested_child_window->GetProperty(testing_atom_);
|
| + EXPECT_TRUE(prop);
|
| + EXPECT_EQ(XA_CARDINAL, prop->type());
|
| + EXPECT_EQ(8, prop->format());
|
| + EXPECT_EQ(1, prop->length());
|
| + EXPECT_EQ(0x42, *static_cast<const uint8_t*>(prop->data()));
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, ResetCacheTest) {
|
| + XID child1_xid = CreateWindow(cache_root_);
|
| + XID child2_xid = CreateWindow(cache_root_);
|
| +
|
| + auto verify = [&]() {
|
| + auto parent_window = cache_->GetWindow(cache_root_);
|
| + EXPECT_TRUE(parent_window);
|
| + EXPECT_EQ(2U, parent_window->GetChildren().size());
|
| + auto it = parent_window->GetChildren().begin();
|
| + EXPECT_EQ(child2_xid, (*it)->id());
|
| + EXPECT_EQ(child1_xid, (*++it)->id());
|
| + };
|
| +
|
| + // Normal case.
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + verify();
|
| +
|
| + // Reset when tree is fully-cached.
|
| + Reset();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + verify();
|
| +
|
| + Reset();
|
| + Synchronize();
|
| + // Reset when tree is half-cached.
|
| + Reset();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + verify();
|
| +
|
| + // Multiple resets.
|
| + for (int i = 0; i < 5; i++)
|
| + Reset();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + verify();
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, CreateNotifyAndDestroyNotifyTest) {
|
| + XID child1_xid = CreateWindow(cache_root_);
|
| + XID child2_xid = CreateWindow(cache_root_);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + auto parent_window = cache_->GetWindow(cache_root_);
|
| + EXPECT_TRUE(parent_window);
|
| + EXPECT_EQ(2U, parent_window->GetChildren().size());
|
| + auto it = parent_window->GetChildren().begin();
|
| + EXPECT_EQ(child2_xid, (*it)->id());
|
| + EXPECT_EQ(child1_xid, (*++it)->id());
|
| +
|
| + XDestroyWindow(display_, child1_xid);
|
| + XDestroyWindow(display_, child2_xid);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + parent_window = cache_->GetWindow(cache_root_);
|
| + EXPECT_TRUE(parent_window);
|
| + EXPECT_EQ(0U, parent_window->GetChildren().size());
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, PropertyNotifyTest) {
|
| + SetProperty8(cache_root_, testing_atom_, 0x42);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + auto window = cache_->GetWindow(cache_root_);
|
| + EXPECT_TRUE(window);
|
| + EXPECT_EQ(0U, window->GetChildren().size());
|
| +
|
| + auto prop = window->GetProperty(testing_atom_);
|
| + EXPECT_TRUE(prop);
|
| + EXPECT_EQ(XA_CARDINAL, prop->type());
|
| + EXPECT_EQ(8, prop->format());
|
| + EXPECT_EQ(1, prop->length());
|
| + EXPECT_EQ(0x42, *static_cast<const uint8_t*>(prop->data()));
|
| +
|
| + SetProperty8(cache_root_, testing_atom_, 0x24);
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + prop = window->GetProperty(testing_atom_);
|
| + EXPECT_TRUE(prop);
|
| + EXPECT_EQ(XA_CARDINAL, prop->type());
|
| + EXPECT_EQ(8, prop->format());
|
| + EXPECT_EQ(1, prop->length());
|
| + EXPECT_EQ(0x24, *static_cast<const uint8_t*>(prop->data()));
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, MapNotifyUnmapNotifyTest) {
|
| + // Some window managers treat mapping an override-redirect window as an
|
| + // invitation to add a whole bunch of properties to it. This confuses the
|
| + // EnsureMemoryReclaimed() check since we would be waiting on property
|
| + // requests. Do the map/unmap testing on a child of an unmapped window so it
|
| + // is unviewable and the window manager won't touch it.
|
| + XID window_xid = CreateWindow(cache_root_);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + auto window = cache_->GetWindow(window_xid);
|
| + EXPECT_TRUE(window);
|
| + EXPECT_FALSE(window->is_mapped());
|
| +
|
| + XMapWindow(display_, window_xid);
|
| + Synchronize();
|
| + EXPECT_TRUE(window->is_mapped());
|
| +
|
| + XUnmapWindow(display_, window_xid);
|
| + Synchronize();
|
| + EXPECT_FALSE(window->is_mapped());
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, CirculateNotifyTest) {
|
| + std::list<XID> children_xids;
|
| + for (int i = 0; i < 10; i++) {
|
| + XID child = CreateWindow(cache_root_);
|
| + children_xids.push_front(child);
|
| + XMapWindow(display_, child);
|
| + }
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +
|
| + XID child;
|
| + XCirculateSubwindowsUp(display_, cache_root_);
|
| + child = children_xids.back();
|
| + children_xids.pop_back();
|
| + children_xids.push_front(child);
|
| + Synchronize();
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +
|
| + XCirculateSubwindowsDown(display_, cache_root_);
|
| + child = children_xids.front();
|
| + children_xids.pop_front();
|
| + children_xids.push_back(child);
|
| + Synchronize();
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, ConfigureNotifyTest) {
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + auto window = cache_->GetWindow(cache_root_);
|
| + EXPECT_TRUE(window);
|
| +
|
| + EXPECT_EQ(0, window->x());
|
| + EXPECT_EQ(0, window->y());
|
| + XMoveWindow(display_, cache_root_, 1, 1);
|
| + Synchronize();
|
| + EXPECT_EQ(1, window->x());
|
| + EXPECT_EQ(1, window->y());
|
| +
|
| + EXPECT_EQ(1U, window->width());
|
| + EXPECT_EQ(1U, window->height());
|
| + XResizeWindow(display_, cache_root_, 2, 2);
|
| + Synchronize();
|
| + EXPECT_EQ(2U, window->width());
|
| + EXPECT_EQ(2U, window->height());
|
| +
|
| + EXPECT_EQ(0U, window->border_width());
|
| + XSetWindowBorderWidth(display_, cache_root_, 1);
|
| + Synchronize();
|
| + EXPECT_EQ(1U, window->border_width());
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, StackingOrderTest) {
|
| + std::vector<XID> children_xids;
|
| + for (int i = 0; i < 10; i++) {
|
| + XID child = CreateWindow(cache_root_);
|
| + children_xids.push_back(child);
|
| + XMapWindow(display_, child);
|
| + }
|
| + std::reverse(children_xids.begin(), children_xids.end());
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +
|
| + // XRaiseWindow
|
| + // Raise window 5
|
| + XID child = children_xids[5];
|
| + XRaiseWindow(display_, child);
|
| + children_xids.erase(children_xids.begin() + 5);
|
| + children_xids.insert(children_xids.begin(), child);
|
| + Synchronize();
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +
|
| + // XLowerWindow
|
| + // Lower window 5
|
| + child = children_xids[5];
|
| + XLowerWindow(display_, child);
|
| + children_xids.erase(children_xids.begin() + 5);
|
| + children_xids.insert(children_xids.end(), child);
|
| + Synchronize();
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +
|
| + // XRestackWindows
|
| + // Stack window 2 below window 6
|
| + XID restack_windows[2] = {children_xids[6], children_xids[2]};
|
| + child = children_xids[2];
|
| + children_xids.erase(children_xids.begin() + 2);
|
| + children_xids.insert(children_xids.begin() + 6, child);
|
| + XRestackWindows(display_, restack_windows, 2);
|
| + Synchronize();
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +
|
| + // XConfigureWindow
|
| + // Stack window 2 below window 6
|
| + XWindowChanges changes;
|
| + child = children_xids[2];
|
| + changes.sibling = children_xids[6];
|
| + changes.stack_mode = Below;
|
| + children_xids.erase(children_xids.begin() + 2);
|
| + children_xids.insert(children_xids.begin() + 6, child);
|
| + XConfigureWindow(display_, child, CWSibling | CWStackMode, &changes);
|
| + Synchronize();
|
| + VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids);
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, GravityNotifyTest) {
|
| + XID child_xid = CreateWindow(cache_root_);
|
| +
|
| + XSetWindowAttributes swa;
|
| + swa.bit_gravity = ForgetGravity;
|
| + swa.win_gravity = NorthEastGravity;
|
| + XChangeWindowAttributes(display_, child_xid, CWBitGravity | CWWinGravity,
|
| + &swa);
|
| +
|
| + XResizeWindow(display_, cache_root_, 100, 100);
|
| + XResizeWindow(display_, child_xid, 25, 25);
|
| + XMoveWindow(display_, child_xid, 75, 0);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| +
|
| + auto parent = cache_->GetWindow(cache_root_);
|
| + EXPECT_TRUE(parent);
|
| + EXPECT_EQ(0, parent->x());
|
| + EXPECT_EQ(0, parent->y());
|
| + EXPECT_EQ(100U, parent->width());
|
| + EXPECT_EQ(100U, parent->height());
|
| + auto child = cache_->GetWindow(child_xid);
|
| + EXPECT_TRUE(child);
|
| + EXPECT_EQ(75, child->x());
|
| + EXPECT_EQ(0, child->y());
|
| +
|
| + XResizeWindow(display_, cache_root_, 50, 50);
|
| + Synchronize();
|
| +
|
| + EXPECT_EQ(25, child->x());
|
| + EXPECT_EQ(0, child->y());
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, ReparentNotifyTest) {
|
| + // Start with this tree:
|
| + //
|
| + // child1 -- grandchild
|
| + // /
|
| + // parent
|
| + // \
|
| + // child2
|
| + //
|
| + XID child1_xid = CreateWindow(cache_root_);
|
| + XID child2_xid = CreateWindow(cache_root_);
|
| + XID grandchild_xid = CreateWindow(child1_xid);
|
| +
|
| + Synchronize();
|
| + EXPECT_TRUE(BlockUntilTreeIsCached());
|
| + auto parent = cache_->GetWindow(cache_root_);
|
| + auto child1 = cache_->GetWindow(child1_xid);
|
| + auto child2 = cache_->GetWindow(child2_xid);
|
| + auto grandchild = cache_->GetWindow(grandchild_xid);
|
| +
|
| + EXPECT_TRUE(parent);
|
| + EXPECT_TRUE(child1);
|
| + EXPECT_TRUE(child2);
|
| + EXPECT_TRUE(grandchild);
|
| + EXPECT_EQ(2U, parent->GetChildren().size());
|
| + EXPECT_EQ(1U, child1->GetChildren().size());
|
| + EXPECT_EQ(0U, child2->GetChildren().size());
|
| + EXPECT_EQ(0U, grandchild->GetChildren().size());
|
| + EXPECT_EQ(grandchild, *child1->GetChildren().begin());
|
| + EXPECT_EQ(child1, grandchild->parent());
|
| +
|
| + // Reparent grandchild so the tree now looks like this:
|
| + //
|
| + // child1
|
| + // /
|
| + // parent
|
| + // \
|
| + // child2 -- grandchild
|
| + //
|
| + XReparentWindow(display_, grandchild_xid, child2_xid, 0, 0);
|
| + Synchronize();
|
| +
|
| + EXPECT_EQ(2U, parent->GetChildren().size());
|
| + EXPECT_EQ(0U, child1->GetChildren().size());
|
| + EXPECT_EQ(1U, child2->GetChildren().size());
|
| + EXPECT_EQ(0U, grandchild->GetChildren().size());
|
| + EXPECT_EQ(grandchild, *child2->GetChildren().begin());
|
| + EXPECT_EQ(child2, grandchild->parent());
|
| +}
|
| +
|
| +TEST_F(XWindowCacheTest, WindowAndPropertyValidationTest) {
|
| + std::vector<XID> window_chain;
|
| + XID window_xid = cache_root_;
|
| + for(int i = 0; i < 10; i++)
|
| + window_xid = CreateWindow(window_xid);
|
| + XResizeWindow(display_, window_xid, 2, 2);
|
| + SetProperty8(window_xid, testing_atom_, 0x42);
|
| + Synchronize();
|
| +
|
| + auto window = cache_->GetWindow(window_xid);
|
| + DCHECK(window);
|
| + DCHECK_EQ(2, window->width());
|
| + DCHECK_EQ(2, window->height());
|
| +
|
| + auto prop = window->GetProperty(testing_atom_);
|
| + EXPECT_TRUE(prop);
|
| + EXPECT_EQ(XA_CARDINAL, prop->type());
|
| + EXPECT_EQ(8, prop->format());
|
| + EXPECT_EQ(1, prop->length());
|
| + EXPECT_EQ(0x42, *static_cast<const uint8_t*>(prop->data()));
|
| +}
|
| +
|
| +} // namespace ui
|
|
|