| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "ui/base/x/x11_window_cache.h" |
| 6 |
| 7 #include <poll.h> |
| 8 #include <X11/Xlib.h> |
| 9 #include <X11/Xatom.h> |
| 10 |
| 11 // These macros defined by Xlib conflict with gtest |
| 12 #undef None |
| 13 #undef Bool |
| 14 |
| 15 #include "base/macros.h" |
| 16 #include "base/posix/eintr_wrapper.h" |
| 17 #include "base/time/time.h" |
| 18 #include "testing/gtest/include/gtest/gtest.h" |
| 19 #include "ui/events/platform/x11/x11_event_source.h" |
| 20 #include "ui/gfx/x/x11_types.h" |
| 21 |
| 22 namespace ui { |
| 23 |
| 24 class XWindowCacheTest; |
| 25 |
| 26 class TestX11EventSourceDelegate : public X11EventSourceDelegate { |
| 27 public: |
| 28 TestX11EventSourceDelegate(XWindowCacheTest* cache_test) |
| 29 : cache_(nullptr), cache_test_(cache_test) {} |
| 30 |
| 31 void SetCache(XWindowCache* cache) { cache_ = cache; } |
| 32 |
| 33 protected: |
| 34 void ProcessXEvent(XEvent* xevent) override; |
| 35 |
| 36 private: |
| 37 XWindowCache* cache_; |
| 38 XWindowCacheTest* cache_test_; |
| 39 }; |
| 40 |
| 41 class TestX11EventSource : public X11EventSource { |
| 42 public: |
| 43 TestX11EventSource(X11EventSourceDelegate* delegate, XDisplay* display) |
| 44 : X11EventSource(delegate, display) {} |
| 45 |
| 46 // Returns false iff there was a connection error or a timeout. |
| 47 bool BlockUntilConnectionIsReadable() { |
| 48 int conn_fd = ConnectionNumber(display_); |
| 49 |
| 50 struct pollfd rfd; |
| 51 rfd.fd = conn_fd; |
| 52 rfd.events = POLLIN; |
| 53 rfd.revents = 0; |
| 54 |
| 55 static constexpr unsigned int timeout_ms = 3000; |
| 56 base::TimeTicks deadline = |
| 57 base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(timeout_ms); |
| 58 HANDLE_EINTR(poll( |
| 59 &rfd, 1, |
| 60 std::max(0L, (deadline - base::TimeTicks::Now()).InMilliseconds()))); |
| 61 return rfd.revents & POLLIN; |
| 62 } |
| 63 |
| 64 // Ensures we have the state of the entire window tree. May block. Returns |
| 65 // false iff there was a connection error or a timeout. |
| 66 bool BlockUntilTreeIsCached() { |
| 67 while (!request_queue_.empty()) { |
| 68 if (!HasNextReply()) { |
| 69 if (!BlockUntilConnectionIsReadable()) |
| 70 return false; |
| 71 } |
| 72 DispatchXEvents(); |
| 73 } |
| 74 return true; |
| 75 } |
| 76 |
| 77 // Ensures we have processed all events sent server-side before Synchronize |
| 78 // was called client-side. We may be forced to wait for more events to come |
| 79 // that are not yet in the queue. You most likely want to |
| 80 // BlockUntilWindowTreeIsCached before and after calling Synchronize. Blocks. |
| 81 // Returns false iff there was a connection error or a timeout. |
| 82 void Synchronize() { |
| 83 XSync(display_, False); |
| 84 DispatchXEvents(); |
| 85 } |
| 86 |
| 87 size_t NumberOfQueuedRequests() { return request_queue_.size(); } |
| 88 |
| 89 private: |
| 90 }; |
| 91 |
| 92 class XWindowCacheTest : public testing::Test { |
| 93 public: |
| 94 XWindowCacheTest() |
| 95 : display_(gfx::GetXDisplay()), |
| 96 display_root_(DefaultRootWindow(display_)), |
| 97 cache_root_(XCB_WINDOW_NONE), |
| 98 testing_atom_(XInternAtom(display_, "CHROMIUM_TESTING_ATOM", False)), |
| 99 delegate_(this), |
| 100 event_source_(&delegate_, display_), |
| 101 cache_(nullptr) {} |
| 102 ~XWindowCacheTest() override {} |
| 103 |
| 104 void SetUp() override { |
| 105 cache_root_ = CreateWindow(display_root_); |
| 106 cache_ = new XWindowCache(display_, &event_source_, cache_root_); |
| 107 delegate_.SetCache(cache_); |
| 108 } |
| 109 |
| 110 void TearDown() override { |
| 111 XDestroyWindow(display_, cache_root_); |
| 112 cache_root_ = XCB_WINDOW_NONE; |
| 113 EnsureMemoryReclaimed(); |
| 114 delete cache_; |
| 115 cache_ = nullptr; |
| 116 delegate_.SetCache(cache_); |
| 117 } |
| 118 |
| 119 void EnsureMemoryReclaimed() { |
| 120 Synchronize(); |
| 121 EXPECT_TRUE(cache_->windows_.empty()); |
| 122 EXPECT_EQ(0U, event_source_.NumberOfQueuedRequests()); |
| 123 } |
| 124 |
| 125 XID CreateWindow(XID parent) { |
| 126 XSetWindowAttributes swa; |
| 127 swa.override_redirect = True; |
| 128 return XCreateWindow(display_, parent, 0, 0, 1, 1, 0, CopyFromParent, |
| 129 InputOutput, CopyFromParent, CWOverrideRedirect, &swa); |
| 130 } |
| 131 |
| 132 void ProcessEvent(XWindowCache* cache, XEvent* event) { |
| 133 cache->ProcessEvent(event); |
| 134 } |
| 135 |
| 136 bool BlockUntilTreeIsCached() { |
| 137 return event_source_.BlockUntilTreeIsCached(); |
| 138 } |
| 139 |
| 140 void Synchronize() { event_source_.Synchronize(); } |
| 141 |
| 142 void Reset() { cache_->ResetCacheImpl(); } |
| 143 |
| 144 void SetProperty8(XID window, XID property, uint8_t value) { |
| 145 XChangeProperty(display_, window, property, XA_CARDINAL, 8, PropModeReplace, |
| 146 &value, 1); |
| 147 } |
| 148 |
| 149 template <typename T> |
| 150 void VerifyStackingOrder(const XWindowCache::Window* window, |
| 151 const T& container) { |
| 152 EXPECT_TRUE(window); |
| 153 EXPECT_EQ(window->children.size(), container.size()); |
| 154 auto it = window->children.begin(); |
| 155 for (XID id : container) |
| 156 EXPECT_EQ(id, (*it++)->id); |
| 157 } |
| 158 |
| 159 XDisplay* display_; |
| 160 XID display_root_; |
| 161 XID cache_root_; |
| 162 XAtom testing_atom_; |
| 163 |
| 164 TestX11EventSourceDelegate delegate_; |
| 165 TestX11EventSource event_source_; |
| 166 XWindowCache* cache_; |
| 167 |
| 168 private: |
| 169 DISALLOW_COPY_AND_ASSIGN(XWindowCacheTest); |
| 170 }; |
| 171 |
| 172 void TestX11EventSourceDelegate::ProcessXEvent(XEvent* xevent) { |
| 173 if (cache_) |
| 174 cache_test_->ProcessEvent(cache_, xevent); |
| 175 } |
| 176 |
| 177 TEST_F(XWindowCacheTest, BasicTest) { |
| 178 XID child1_xid = CreateWindow(cache_root_); |
| 179 XID child2_xid = CreateWindow(cache_root_); |
| 180 |
| 181 Synchronize(); |
| 182 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 183 |
| 184 auto parent_window = cache_->GetWindow(cache_root_); |
| 185 EXPECT_TRUE(parent_window); |
| 186 EXPECT_EQ(parent_window->children.size(), 2U); |
| 187 auto it = parent_window->children.begin(); |
| 188 EXPECT_EQ((*it++)->id, child2_xid); |
| 189 EXPECT_EQ((*it++)->id, child1_xid); |
| 190 } |
| 191 |
| 192 TEST_F(XWindowCacheTest, NestingTest) { |
| 193 XID child_xid = CreateWindow(cache_root_); |
| 194 XID nested_child_xid = CreateWindow(child_xid); |
| 195 SetProperty8(nested_child_xid, testing_atom_, 0x42); |
| 196 |
| 197 Synchronize(); |
| 198 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 199 |
| 200 auto parent_window = cache_->GetWindow(cache_root_); |
| 201 EXPECT_TRUE(parent_window); |
| 202 EXPECT_FALSE(parent_window->children_request); |
| 203 EXPECT_EQ(parent_window->children.size(), 1U); |
| 204 EXPECT_FALSE(parent_window->properties_request); |
| 205 EXPECT_EQ(parent_window->properties.size(), 0U); |
| 206 |
| 207 auto child_window = parent_window->children.front().get(); |
| 208 EXPECT_TRUE(child_window); |
| 209 EXPECT_FALSE(child_window->children_request); |
| 210 EXPECT_EQ(child_window->children.size(), 1U); |
| 211 EXPECT_FALSE(child_window->properties_request); |
| 212 EXPECT_EQ(child_window->properties.size(), 0U); |
| 213 |
| 214 auto nested_child_window = child_window->children.front().get(); |
| 215 EXPECT_TRUE(nested_child_window); |
| 216 EXPECT_FALSE(nested_child_window->children_request); |
| 217 EXPECT_EQ(nested_child_window->children.size(), 0U); |
| 218 EXPECT_FALSE(nested_child_window->properties_request); |
| 219 EXPECT_EQ(nested_child_window->properties.size(), 1U); |
| 220 |
| 221 auto prop = nested_child_window->GetProperty(testing_atom_); |
| 222 EXPECT_TRUE(prop); |
| 223 EXPECT_FALSE(prop->property_request); |
| 224 EXPECT_EQ(prop->type, XA_CARDINAL); |
| 225 EXPECT_EQ(prop->data_format, 8); |
| 226 EXPECT_EQ(prop->data_length, 1); |
| 227 EXPECT_EQ(prop->data.bits_8[0], 0x42); |
| 228 } |
| 229 |
| 230 TEST_F(XWindowCacheTest, ResetCacheTest) { |
| 231 XID child1_xid = CreateWindow(cache_root_); |
| 232 XID child2_xid = CreateWindow(cache_root_); |
| 233 |
| 234 auto verify = [&]() { |
| 235 auto parent_window = cache_->GetWindow(cache_root_); |
| 236 EXPECT_TRUE(parent_window); |
| 237 EXPECT_EQ(parent_window->children.size(), 2U); |
| 238 auto it = parent_window->children.begin(); |
| 239 EXPECT_EQ((*it++)->id, child2_xid); |
| 240 EXPECT_EQ((*it++)->id, child1_xid); |
| 241 }; |
| 242 |
| 243 // Normal case. |
| 244 Synchronize(); |
| 245 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 246 verify(); |
| 247 |
| 248 // Reset when tree is fully-cached. |
| 249 Reset(); |
| 250 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 251 verify(); |
| 252 |
| 253 Reset(); |
| 254 Synchronize(); |
| 255 // Reset when tree is half-cached. |
| 256 Reset(); |
| 257 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 258 verify(); |
| 259 |
| 260 // Multiple resets. |
| 261 for (int i = 0; i < 5; i++) |
| 262 Reset(); |
| 263 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 264 verify(); |
| 265 } |
| 266 |
| 267 TEST_F(XWindowCacheTest, CreateNotifyAndDestroyNotifyTest) { |
| 268 XID child1_xid = CreateWindow(cache_root_); |
| 269 XID child2_xid = CreateWindow(cache_root_); |
| 270 |
| 271 Synchronize(); |
| 272 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 273 |
| 274 auto parent_window = cache_->GetWindow(cache_root_); |
| 275 EXPECT_TRUE(parent_window); |
| 276 EXPECT_EQ(parent_window->children.size(), 2U); |
| 277 auto it = parent_window->children.begin(); |
| 278 EXPECT_EQ((*it++)->id, child2_xid); |
| 279 EXPECT_EQ((*it++)->id, child1_xid); |
| 280 |
| 281 XDestroyWindow(display_, child1_xid); |
| 282 XDestroyWindow(display_, child2_xid); |
| 283 |
| 284 Synchronize(); |
| 285 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 286 |
| 287 parent_window = cache_->GetWindow(cache_root_); |
| 288 EXPECT_TRUE(parent_window); |
| 289 EXPECT_EQ(parent_window->children.size(), 0U); |
| 290 } |
| 291 |
| 292 TEST_F(XWindowCacheTest, PropertyNotifyTest) { |
| 293 SetProperty8(cache_root_, testing_atom_, 0x42); |
| 294 |
| 295 Synchronize(); |
| 296 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 297 |
| 298 auto window = cache_->GetWindow(cache_root_); |
| 299 EXPECT_TRUE(window); |
| 300 EXPECT_EQ(window->children.size(), 0U); |
| 301 EXPECT_EQ(window->properties.size(), 1U); |
| 302 |
| 303 auto prop = window->GetProperty(testing_atom_); |
| 304 EXPECT_TRUE(prop); |
| 305 EXPECT_FALSE(prop->property_request); |
| 306 EXPECT_EQ(prop->type, XA_CARDINAL); |
| 307 EXPECT_EQ(prop->data_format, 8); |
| 308 EXPECT_EQ(prop->data_length, 1); |
| 309 EXPECT_EQ(prop->data.bits_8[0], 0x42); |
| 310 |
| 311 SetProperty8(cache_root_, testing_atom_, 0x24); |
| 312 Synchronize(); |
| 313 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 314 |
| 315 prop = window->GetProperty(testing_atom_); |
| 316 EXPECT_TRUE(prop); |
| 317 EXPECT_FALSE(prop->property_request); |
| 318 EXPECT_EQ(prop->type, XA_CARDINAL); |
| 319 EXPECT_EQ(prop->data_format, 8); |
| 320 EXPECT_EQ(prop->data_length, 1); |
| 321 EXPECT_EQ(prop->data.bits_8[0], 0x24); |
| 322 } |
| 323 |
| 324 TEST_F(XWindowCacheTest, MapNotifyUnmapNotifyTest) { |
| 325 // Some window managers treat mapping an override-redirect window as an |
| 326 // invitation to add a whole bunch of properties to it. This confuses the |
| 327 // EnsureMemoryReclaimed() check since we would be waiting on property |
| 328 // requests. Do the map/unmap testing on a child of an unmapped window so it |
| 329 // is unviewable and the window manager won't touch it. |
| 330 XID window_xid = CreateWindow(cache_root_); |
| 331 |
| 332 Synchronize(); |
| 333 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 334 |
| 335 auto window = cache_->GetWindow(window_xid); |
| 336 EXPECT_TRUE(window); |
| 337 EXPECT_FALSE(window->attributes_request); |
| 338 EXPECT_FALSE(window->is_mapped); |
| 339 |
| 340 XMapWindow(display_, window_xid); |
| 341 Synchronize(); |
| 342 EXPECT_TRUE(window->is_mapped); |
| 343 |
| 344 XUnmapWindow(display_, window_xid); |
| 345 Synchronize(); |
| 346 EXPECT_FALSE(window->is_mapped); |
| 347 } |
| 348 |
| 349 TEST_F(XWindowCacheTest, CirculateNotifyTest) { |
| 350 std::list<XID> children_xids; |
| 351 for (int i = 0; i < 10; i++) { |
| 352 XID child = CreateWindow(cache_root_); |
| 353 children_xids.push_front(child); |
| 354 XMapWindow(display_, child); |
| 355 } |
| 356 |
| 357 Synchronize(); |
| 358 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 359 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 360 |
| 361 XID child; |
| 362 XCirculateSubwindowsUp(display_, cache_root_); |
| 363 child = children_xids.back(); |
| 364 children_xids.pop_back(); |
| 365 children_xids.push_front(child); |
| 366 Synchronize(); |
| 367 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 368 |
| 369 XCirculateSubwindowsDown(display_, cache_root_); |
| 370 child = children_xids.front(); |
| 371 children_xids.pop_front(); |
| 372 children_xids.push_back(child); |
| 373 Synchronize(); |
| 374 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 375 } |
| 376 |
| 377 TEST_F(XWindowCacheTest, ConfigureNotifyTest) { |
| 378 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 379 auto window = cache_->GetWindow(cache_root_); |
| 380 EXPECT_TRUE(window); |
| 381 EXPECT_FALSE(window->attributes_request); |
| 382 EXPECT_FALSE(window->geometry_request); |
| 383 |
| 384 EXPECT_EQ(window->x, 0); |
| 385 EXPECT_EQ(window->y, 0); |
| 386 XMoveWindow(display_, cache_root_, 1, 1); |
| 387 Synchronize(); |
| 388 EXPECT_EQ(window->x, 1); |
| 389 EXPECT_EQ(window->y, 1); |
| 390 |
| 391 EXPECT_EQ(window->width, 1U); |
| 392 EXPECT_EQ(window->height, 1U); |
| 393 XResizeWindow(display_, cache_root_, 2, 2); |
| 394 Synchronize(); |
| 395 EXPECT_EQ(window->width, 2U); |
| 396 EXPECT_EQ(window->height, 2U); |
| 397 |
| 398 EXPECT_EQ(window->border_width, 0U); |
| 399 XSetWindowBorderWidth(display_, cache_root_, 1); |
| 400 Synchronize(); |
| 401 EXPECT_EQ(window->border_width, 1U); |
| 402 } |
| 403 |
| 404 TEST_F(XWindowCacheTest, StackingOrderTest) { |
| 405 std::vector<XID> children_xids; |
| 406 for (int i = 0; i < 10; i++) { |
| 407 XID child = CreateWindow(cache_root_); |
| 408 children_xids.push_back(child); |
| 409 XMapWindow(display_, child); |
| 410 } |
| 411 std::reverse(children_xids.begin(), children_xids.end()); |
| 412 |
| 413 Synchronize(); |
| 414 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 415 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 416 |
| 417 // XRaiseWindow |
| 418 // Raise window 5 |
| 419 XID child = children_xids[5]; |
| 420 XRaiseWindow(display_, child); |
| 421 children_xids.erase(children_xids.begin() + 5); |
| 422 children_xids.insert(children_xids.begin(), child); |
| 423 Synchronize(); |
| 424 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 425 |
| 426 // XLowerWindow |
| 427 // Lower window 5 |
| 428 child = children_xids[5]; |
| 429 XLowerWindow(display_, child); |
| 430 children_xids.erase(children_xids.begin() + 5); |
| 431 children_xids.insert(children_xids.end(), child); |
| 432 Synchronize(); |
| 433 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 434 |
| 435 // XRestackWindows |
| 436 // Stack window 2 below window 6 |
| 437 XID restack_windows[2] = {children_xids[6], children_xids[2]}; |
| 438 child = children_xids[2]; |
| 439 children_xids.erase(children_xids.begin() + 2); |
| 440 children_xids.insert(children_xids.begin() + 6, child); |
| 441 XRestackWindows(display_, restack_windows, 2); |
| 442 Synchronize(); |
| 443 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 444 |
| 445 // XConfigureWindow |
| 446 // Stack window 2 below window 6 |
| 447 XWindowChanges changes; |
| 448 child = children_xids[2]; |
| 449 changes.sibling = children_xids[6]; |
| 450 changes.stack_mode = Below; |
| 451 children_xids.erase(children_xids.begin() + 2); |
| 452 children_xids.insert(children_xids.begin() + 6, child); |
| 453 XConfigureWindow(display_, child, CWSibling | CWStackMode, &changes); |
| 454 Synchronize(); |
| 455 VerifyStackingOrder(cache_->GetWindow(cache_root_), children_xids); |
| 456 } |
| 457 |
| 458 TEST_F(XWindowCacheTest, GravityNotifyTest) { |
| 459 XID child_xid = CreateWindow(cache_root_); |
| 460 |
| 461 XSetWindowAttributes swa; |
| 462 swa.bit_gravity = ForgetGravity; |
| 463 swa.win_gravity = NorthEastGravity; |
| 464 XChangeWindowAttributes(display_, child_xid, CWBitGravity | CWWinGravity, |
| 465 &swa); |
| 466 |
| 467 XResizeWindow(display_, cache_root_, 100, 100); |
| 468 XResizeWindow(display_, child_xid, 25, 25); |
| 469 XMoveWindow(display_, child_xid, 75, 0); |
| 470 |
| 471 Synchronize(); |
| 472 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 473 |
| 474 auto parent = cache_->GetWindow(cache_root_); |
| 475 EXPECT_TRUE(parent); |
| 476 EXPECT_EQ(parent->x, 0); |
| 477 EXPECT_EQ(parent->y, 0); |
| 478 EXPECT_EQ(parent->width, 100U); |
| 479 EXPECT_EQ(parent->height, 100U); |
| 480 auto child = cache_->GetWindow(child_xid); |
| 481 EXPECT_TRUE(child); |
| 482 EXPECT_EQ(child->x, 75); |
| 483 EXPECT_EQ(child->y, 0); |
| 484 |
| 485 XResizeWindow(display_, cache_root_, 50, 50); |
| 486 Synchronize(); |
| 487 |
| 488 EXPECT_EQ(child->x, 25); |
| 489 EXPECT_EQ(child->y, 0); |
| 490 } |
| 491 |
| 492 TEST_F(XWindowCacheTest, ReparentNotifyTest) { |
| 493 // Start with this tree: |
| 494 // |
| 495 // child1 -- grandchild |
| 496 // / |
| 497 // parent |
| 498 // \ |
| 499 // child2 |
| 500 // |
| 501 XID child1_xid = CreateWindow(cache_root_); |
| 502 XID child2_xid = CreateWindow(cache_root_); |
| 503 XID grandchild_xid = CreateWindow(child1_xid); |
| 504 |
| 505 Synchronize(); |
| 506 EXPECT_TRUE(BlockUntilTreeIsCached()); |
| 507 auto parent = cache_->GetWindow(cache_root_); |
| 508 auto child1 = cache_->GetWindow(child1_xid); |
| 509 auto child2 = cache_->GetWindow(child2_xid); |
| 510 auto grandchild = cache_->GetWindow(grandchild_xid); |
| 511 |
| 512 EXPECT_TRUE(parent); |
| 513 EXPECT_TRUE(child1); |
| 514 EXPECT_TRUE(child2); |
| 515 EXPECT_TRUE(grandchild); |
| 516 EXPECT_EQ(parent->children.size(), 2U); |
| 517 EXPECT_EQ(child1->children.size(), 1U); |
| 518 EXPECT_EQ(child2->children.size(), 0U); |
| 519 EXPECT_EQ(grandchild->children.size(), 0U); |
| 520 EXPECT_EQ(child1->children.front().get(), grandchild); |
| 521 EXPECT_EQ(grandchild->parent, child1); |
| 522 |
| 523 // Reparent grandchild so the tree now looks like this: |
| 524 // |
| 525 // child1 |
| 526 // / |
| 527 // parent |
| 528 // \ |
| 529 // child2 -- grandchild |
| 530 // |
| 531 XReparentWindow(display_, grandchild_xid, child2_xid, 0, 0); |
| 532 Synchronize(); |
| 533 |
| 534 EXPECT_EQ(parent->children.size(), 2U); |
| 535 EXPECT_EQ(child1->children.size(), 0U); |
| 536 EXPECT_EQ(child2->children.size(), 1U); |
| 537 EXPECT_EQ(grandchild->children.size(), 0U); |
| 538 EXPECT_EQ(child2->children.front().get(), grandchild); |
| 539 EXPECT_EQ(grandchild->parent, child2); |
| 540 } |
| 541 |
| 542 } // namespace ui |
| OLD | NEW |