Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/layout/LayoutBoxModelObject.h" | 5 #include "core/layout/LayoutBoxModelObject.h" |
| 6 | 6 |
| 7 #include "core/dom/DOMTokenList.h" | 7 #include "core/dom/DOMTokenList.h" |
| 8 #include "core/dom/DocumentLifecycle.h" | 8 #include "core/dom/DocumentLifecycle.h" |
| 9 #include "core/html/HTMLElement.h" | 9 #include "core/html/HTMLElement.h" |
| 10 #include "core/layout/ImageQualityController.h" | 10 #include "core/layout/ImageQualityController.h" |
| (...skipping 310 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 321 | 321 |
| 322 // After updating compositing inputs we should have the updated position. | 322 // After updating compositing inputs we should have the updated position. |
| 323 document().view()->updateAllLifecyclePhases(); | 323 document().view()->updateAllLifecyclePhases(); |
| 324 EXPECT_EQ(50.f, | 324 EXPECT_EQ(50.f, |
| 325 getScrollContainerRelativeStickyBoxRect( | 325 getScrollContainerRelativeStickyBoxRect( |
| 326 scrollableArea->stickyConstraintsMap().get(sticky->layer())) | 326 scrollableArea->stickyConstraintsMap().get(sticky->layer())) |
| 327 .location() | 327 .location() |
| 328 .x()); | 328 .x()); |
| 329 } | 329 } |
| 330 | 330 |
| 331 // Verifies that the correct sticky-box shifting ancestor is found when | |
| 332 // computing the sticky constraints. Any such ancestor is the first sticky | |
| 333 // element between you and your containing block (exclusive). | |
| 334 // | |
| 335 // In most cases, this pointer should be null since your parent is normally your | |
| 336 // containing block. However there are cases where this is not true, including | |
| 337 // inline blocks and tables. The latter is currently irrelevant since only table | |
| 338 // cells can be sticky in CSS2.1, but we can test the former. | |
| 339 TEST_F(LayoutBoxModelObjectTest, | |
| 340 StickyPositionFindsCorrectStickyBoxShiftingAncestor) { | |
| 341 setBodyInnerHTML( | |
| 342 "<style>#stickyOuterDiv { position: sticky; }" | |
| 343 "#stickyOuterInline { position: sticky; display: inline; }" | |
| 344 "#stickyInnerInline { position: sticky; display: inline; }</style>" | |
| 345 "<div id='stickyOuterDiv'><div id='stickyOuterInline'>" | |
| 346 "<div id='stickyInnerInline'></div></div></div>"); | |
| 347 | |
| 348 // Force the constraints to be calculated. | |
|
flackr
2017/02/22 22:40:14
Maybe just updateAllLifecyclePhases?
smcgruer
2017/02/23 20:14:17
It turns out that setBodyInnerHTML(..) does exactl
| |
| 349 LayoutBoxModelObject* stickyOuterDiv = | |
| 350 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuterDiv")); | |
| 351 stickyOuterDiv->updateStickyPositionConstraints(); | |
| 352 | |
| 353 LayoutBoxModelObject* stickyOuterInline = | |
| 354 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuterInline")); | |
| 355 stickyOuterInline->updateStickyPositionConstraints(); | |
| 356 | |
| 357 LayoutBoxModelObject* stickyInnerInline = | |
| 358 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyInnerInline")); | |
| 359 stickyInnerInline->updateStickyPositionConstraints(); | |
| 360 | |
| 361 PaintLayerScrollableArea* scrollableArea = | |
| 362 stickyOuterDiv->layer()->ancestorOverflowLayer()->getScrollableArea(); | |
| 363 ASSERT_TRUE(scrollableArea); | |
| 364 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); | |
| 365 | |
| 366 ASSERT_TRUE(constraintsMap.contains(stickyOuterDiv->layer())); | |
| 367 ASSERT_TRUE(constraintsMap.contains(stickyOuterInline->layer())); | |
| 368 ASSERT_TRUE(constraintsMap.contains(stickyInnerInline->layer())); | |
| 369 | |
| 370 // The outer block element trivially has no sticky-box shifting ancestor. | |
| 371 EXPECT_FALSE(constraintsMap.get(stickyOuterDiv->layer()) | |
| 372 .nearestStickyElementShiftingStickyBox()); | |
| 373 | |
| 374 // Neither does the outer inline element, as its parent element is also its | |
| 375 // containing block. | |
| 376 EXPECT_FALSE(constraintsMap.get(stickyOuterInline->layer()) | |
| 377 .nearestStickyElementShiftingStickyBox()); | |
| 378 | |
| 379 // However the inner inline element does have a sticky-box shifting ancestor, | |
| 380 // as its containing block is the ancestor block element, not its parent. | |
| 381 EXPECT_EQ(stickyOuterInline, | |
| 382 constraintsMap.get(stickyInnerInline->layer()) | |
| 383 .nearestStickyElementShiftingStickyBox()); | |
| 384 } | |
| 385 | |
| 386 // Verifies that the correct containing-block shifting ancestor is found when | |
| 387 // computing the sticky constraints. Any such ancestor is the first sticky | |
| 388 // element between your containing block (inclusive) and your ancestor overflow | |
| 389 // layer (exclusive). | |
| 390 TEST_F(LayoutBoxModelObjectTest, | |
| 391 StickyPositionFindsCorrectContainingBlockShiftingAncestor) { | |
| 392 // We make the scroller itself sticky in order to check that elements do not | |
| 393 // detect it as their containing-block shifting ancestor. | |
| 394 setBodyInnerHTML( | |
| 395 "<style>#scroller { overflow-y: scroll; position: sticky; }" | |
| 396 "#stickyParent { position: sticky; }" | |
| 397 "#stickyChild { position: sticky; }" | |
| 398 "#stickyNestedChild { position: sticky; }</style>" | |
| 399 "<div id='scroller'><div id='stickyParent'><div id='stickyChild'></div>" | |
| 400 "<div><div id='stickyNestedChild'></div></div></div></div>"); | |
| 401 | |
| 402 // Force the constraints to be calculated. | |
| 403 LayoutBoxModelObject* scroller = | |
| 404 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 405 scroller->updateStickyPositionConstraints(); | |
| 406 | |
| 407 LayoutBoxModelObject* stickyParent = | |
| 408 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); | |
| 409 stickyParent->updateStickyPositionConstraints(); | |
| 410 | |
| 411 LayoutBoxModelObject* stickyChild = | |
| 412 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); | |
| 413 stickyChild->updateStickyPositionConstraints(); | |
| 414 | |
| 415 LayoutBoxModelObject* stickyNestedChild = | |
| 416 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyNestedChild")); | |
| 417 stickyNestedChild->updateStickyPositionConstraints(); | |
| 418 | |
| 419 PaintLayerScrollableArea* scrollableArea = | |
| 420 scroller->layer()->getScrollableArea(); | |
| 421 ASSERT_TRUE(scrollableArea); | |
| 422 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); | |
| 423 | |
| 424 ASSERT_FALSE(constraintsMap.contains(scroller->layer())); | |
| 425 ASSERT_TRUE(constraintsMap.contains(stickyParent->layer())); | |
| 426 ASSERT_TRUE(constraintsMap.contains(stickyChild->layer())); | |
| 427 ASSERT_TRUE(constraintsMap.contains(stickyNestedChild->layer())); | |
| 428 | |
| 429 // The outer <div> should not detect the scroller as its containing-block | |
| 430 // shifting ancestor. | |
| 431 EXPECT_FALSE(constraintsMap.get(stickyParent->layer()) | |
| 432 .nearestStickyElementShiftingContainingBlock()); | |
| 433 | |
| 434 // Both inner children should detect the parent <div> as their | |
| 435 // containing-block shifting ancestor. | |
| 436 EXPECT_EQ(stickyParent, | |
| 437 constraintsMap.get(stickyChild->layer()) | |
| 438 .nearestStickyElementShiftingContainingBlock()); | |
| 439 EXPECT_EQ(stickyParent, | |
| 440 constraintsMap.get(stickyNestedChild->layer()) | |
| 441 .nearestStickyElementShiftingContainingBlock()); | |
| 442 } | |
| 443 | |
| 444 // Verifies that the correct containing-block shifting ancestor is found when | |
| 445 // computing the sticky constraints, in the case where the overflow ancestor is | |
| 446 // the page itself. This is a special-case version of the test above, as we | |
| 447 // often treat the root page as special when it comes to scroll logic. It should | |
| 448 // not make a difference for containing-block shifting ancestor calculations. | |
| 449 TEST_F(LayoutBoxModelObjectTest, | |
| 450 StickyPositionFindsCorrectContainingBlockShiftingAncestorRoot) { | |
| 451 setBodyInnerHTML( | |
| 452 "<style>#stickyParent { position: sticky; }" | |
| 453 "#stickyGrandchild { position: sticky; }</style>" | |
| 454 "<div id='stickyParent'><div><div id='stickyGrandchild'></div></div>" | |
| 455 "</div>"); | |
| 456 | |
| 457 // Force the constraints to be calculated. | |
| 458 | |
| 459 LayoutBoxModelObject* stickyParent = | |
| 460 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); | |
| 461 stickyParent->updateStickyPositionConstraints(); | |
| 462 | |
| 463 LayoutBoxModelObject* stickyGrandchild = | |
| 464 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyGrandchild")); | |
| 465 stickyGrandchild->updateStickyPositionConstraints(); | |
| 466 | |
| 467 PaintLayerScrollableArea* scrollableArea = | |
| 468 stickyParent->layer()->ancestorOverflowLayer()->getScrollableArea(); | |
| 469 ASSERT_TRUE(scrollableArea); | |
| 470 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); | |
| 471 | |
| 472 ASSERT_TRUE(constraintsMap.contains(stickyParent->layer())); | |
| 473 ASSERT_TRUE(constraintsMap.contains(stickyGrandchild->layer())); | |
| 474 | |
| 475 // The grandchild sticky should detect the parent as its containing-block | |
| 476 // shifting ancestor. | |
| 477 EXPECT_EQ(stickyParent, | |
| 478 constraintsMap.get(stickyGrandchild->layer()) | |
| 479 .nearestStickyElementShiftingContainingBlock()); | |
| 480 } | |
| 481 | |
| 482 // Verifies that the correct containing-block shifting ancestor is found when | |
| 483 // computing the sticky constraints, in the case of tables. Tables are unusual | |
| 484 // because the containing block for all table elements is the <table> itself, so | |
| 485 // we have to skip over elements to find the correct ancestor. | |
| 486 TEST_F(LayoutBoxModelObjectTest, | |
| 487 StickyPositionFindsCorrectContainingBlockShiftingAncestorTable) { | |
| 488 setBodyInnerHTML( | |
| 489 "<style>#scroller { overflow-y: scroll; }" | |
| 490 "#stickyOuter { position: sticky; }" | |
| 491 "#stickyTh { position: sticky; }</style>" | |
| 492 "<div id='scroller'><div id='stickyOuter'><table><thead><tr>" | |
| 493 "<th id='stickyTh'></th></tr></thead></table></div></div>"); | |
| 494 | |
| 495 // Force the constraints to be calculated. | |
| 496 LayoutBoxModelObject* scroller = | |
| 497 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 498 scroller->updateStickyPositionConstraints(); | |
| 499 | |
| 500 LayoutBoxModelObject* stickyOuter = | |
| 501 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuter")); | |
| 502 stickyOuter->updateStickyPositionConstraints(); | |
| 503 | |
| 504 LayoutBoxModelObject* stickyTh = | |
| 505 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh")); | |
| 506 stickyTh->updateStickyPositionConstraints(); | |
| 507 | |
| 508 PaintLayerScrollableArea* scrollableArea = | |
| 509 scroller->layer()->getScrollableArea(); | |
| 510 ASSERT_TRUE(scrollableArea); | |
| 511 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); | |
| 512 | |
| 513 ASSERT_FALSE(constraintsMap.contains(scroller->layer())); | |
| 514 ASSERT_TRUE(constraintsMap.contains(stickyOuter->layer())); | |
| 515 ASSERT_TRUE(constraintsMap.contains(stickyTh->layer())); | |
| 516 | |
| 517 // The table cell should detect the outer <div> as its containing-block | |
| 518 // shifting ancestor. | |
| 519 EXPECT_EQ(stickyOuter, | |
| 520 constraintsMap.get(stickyTh->layer()) | |
| 521 .nearestStickyElementShiftingContainingBlock()); | |
| 522 } | |
| 523 | |
| 524 // Verifies that the calculated position:sticky offsets are correct when we have | |
| 525 // a simple case of nested sticky elements. | |
| 526 TEST_F(LayoutBoxModelObjectTest, StickyPositionNested) { | |
| 527 setBodyInnerHTML( | |
| 528 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" | |
| 529 "#prePadding { height: 50px }" | |
| 530 "#stickyParent { position: sticky; top: 0; height: 50px; }" | |
| 531 "#stickyChild { position: sticky; top: 0; height: 25px; }" | |
| 532 "#postPadding { height: 200px }</style>" | |
| 533 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" | |
| 534 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); | |
| 535 | |
| 536 // Make sure that the constraints are cached before scrolling or they may be | |
| 537 // calculated post-scroll and thus not require offset correction. | |
| 538 | |
| 539 LayoutBoxModelObject* stickyParent = | |
| 540 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); | |
| 541 stickyParent->updateStickyPositionConstraints(); | |
| 542 | |
| 543 LayoutBoxModelObject* stickyChild = | |
| 544 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); | |
| 545 stickyChild->updateStickyPositionConstraints(); | |
| 546 | |
| 547 // Scroll the page down. | |
| 548 LayoutBoxModelObject* scroller = | |
| 549 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 550 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 551 scrollableArea->scrollToAbsolutePosition( | |
| 552 FloatPoint(scrollableArea->scrollPosition().x(), 100)); | |
| 553 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); | |
| 554 | |
| 555 // Both the parent and child sticky divs are attempting to place themselves at | |
| 556 // the top of the scrollable area. To achieve this the parent must offset on | |
| 557 // the y-axis against its starting position. The child is offset relative to | |
| 558 // its parent so should not move at all. | |
| 559 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 560 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); | |
| 561 | |
| 562 stickyParent->updateStickyPositionConstraints(); | |
| 563 | |
| 564 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 565 | |
| 566 stickyChild->updateStickyPositionConstraints(); | |
| 567 | |
| 568 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 569 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); | |
| 570 } | |
| 571 | |
| 572 // Verifies that the calculated position:sticky offsets are correct when the | |
| 573 // child has a larger edge constraint value than the parent. | |
| 574 TEST_F(LayoutBoxModelObjectTest, StickyPositionChildHasLargerTop) { | |
| 575 setBodyInnerHTML( | |
| 576 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" | |
| 577 "#prePadding { height: 50px }" | |
| 578 "#stickyParent { position: sticky; top: 0; height: 50px; }" | |
| 579 "#stickyChild { position: sticky; top: 25px; height: 25px; }" | |
| 580 "#postPadding { height: 200px }</style>" | |
| 581 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" | |
| 582 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); | |
| 583 | |
| 584 // Make sure that the constraints are cached before scrolling or they may be | |
| 585 // calculated post-scroll and thus not require offset correction. | |
| 586 | |
| 587 LayoutBoxModelObject* stickyParent = | |
| 588 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); | |
| 589 stickyParent->updateStickyPositionConstraints(); | |
| 590 | |
| 591 LayoutBoxModelObject* stickyChild = | |
| 592 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); | |
| 593 stickyChild->updateStickyPositionConstraints(); | |
| 594 | |
| 595 // Scroll the page down. | |
| 596 LayoutBoxModelObject* scroller = | |
| 597 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 598 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 599 scrollableArea->scrollToAbsolutePosition( | |
| 600 FloatPoint(scrollableArea->scrollPosition().x(), 100)); | |
| 601 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); | |
| 602 | |
| 603 // The parent is attempting to place itself at the top of the scrollable area, | |
| 604 // whilst the child is attempting to be 25 pixels from the top. To achieve | |
| 605 // this both must offset on the y-axis against their starting positions, but | |
| 606 // note the child is offset relative to the parent. | |
| 607 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 608 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); | |
| 609 | |
| 610 stickyParent->updateStickyPositionConstraints(); | |
| 611 | |
| 612 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 613 | |
| 614 stickyChild->updateStickyPositionConstraints(); | |
| 615 | |
| 616 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 617 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); | |
| 618 } | |
| 619 | |
| 620 // Verifies that the calculated position:sticky offsets are correct when the | |
| 621 // child has a smaller edge constraint value than the parent. | |
| 622 TEST_F(LayoutBoxModelObjectTest, StickyPositionParentHasLargerTop) { | |
| 623 setBodyInnerHTML( | |
| 624 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" | |
| 625 "#prePadding { height: 50px }" | |
| 626 "#stickyParent { position: sticky; top: 25px; height: 50px; }" | |
| 627 "#stickyChild { position: sticky; top: 0; height: 25px; }" | |
| 628 "#postPadding { height: 200px }</style>" | |
| 629 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" | |
| 630 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); | |
| 631 | |
| 632 // Make sure that the constraints are cached before scrolling or they may be | |
| 633 // calculated post-scroll and thus not require offset correction. | |
| 634 | |
| 635 LayoutBoxModelObject* stickyParent = | |
| 636 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); | |
| 637 stickyParent->updateStickyPositionConstraints(); | |
| 638 | |
| 639 LayoutBoxModelObject* stickyChild = | |
| 640 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); | |
| 641 stickyChild->updateStickyPositionConstraints(); | |
| 642 | |
| 643 // Scroll the page down. | |
| 644 LayoutBoxModelObject* scroller = | |
| 645 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 646 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 647 scrollableArea->scrollToAbsolutePosition( | |
| 648 FloatPoint(scrollableArea->scrollPosition().x(), 100)); | |
| 649 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); | |
| 650 | |
| 651 // The parent is attempting to place itself 25 pixels from the top of the | |
| 652 // scrollable area, whilst the child is attempting to be at the top. However, | |
| 653 // the child must stay contained within the parent, so it should be pushed | |
| 654 // down to the same height. As always, the child offset is relative. | |
| 655 EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset()); | |
| 656 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); | |
| 657 | |
| 658 stickyParent->updateStickyPositionConstraints(); | |
| 659 | |
| 660 EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset()); | |
| 661 | |
| 662 stickyChild->updateStickyPositionConstraints(); | |
| 663 | |
| 664 EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset()); | |
| 665 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); | |
| 666 } | |
| 667 | |
| 668 // Verifies that the calculated position:sticky offsets are correct when the | |
| 669 // child has a large enough edge constraint value to push outside of its parent. | |
| 670 TEST_F(LayoutBoxModelObjectTest, StickyPositionChildPushingOutsideParent) { | |
| 671 setBodyInnerHTML( | |
| 672 "<style> #scroller { height: 100px; width: 100px; overflow-y: auto; }" | |
| 673 "#prePadding { height: 50px; }" | |
| 674 "#stickyParent { position: sticky; top: 0; height: 50px; }" | |
| 675 "#stickyChild { position: sticky; top: 50px; height: 25px; }" | |
| 676 "#postPadding { height: 200px }</style>" | |
| 677 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" | |
| 678 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); | |
| 679 | |
| 680 // Make sure that the constraints are cached before scrolling or they may be | |
| 681 // calculated post-scroll and thus not require offset correction. | |
| 682 | |
| 683 LayoutBoxModelObject* stickyParent = | |
| 684 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); | |
| 685 stickyParent->updateStickyPositionConstraints(); | |
| 686 | |
| 687 LayoutBoxModelObject* stickyChild = | |
| 688 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); | |
| 689 stickyChild->updateStickyPositionConstraints(); | |
| 690 | |
| 691 // Scroll the page down. | |
| 692 LayoutBoxModelObject* scroller = | |
| 693 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 694 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 695 scrollableArea->scrollToAbsolutePosition( | |
| 696 FloatPoint(scrollableArea->scrollPosition().x(), 100)); | |
| 697 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); | |
| 698 | |
| 699 // The parent is attempting to place itself at the top of the scrollable area, | |
| 700 // whilst the child is attempting to be 50 pixels from the top. However, there | |
| 701 // is only 25 pixels of space for the child to move into, so it should be | |
| 702 // capped by that offset. As always, the child offset is relative. | |
| 703 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 704 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); | |
| 705 | |
| 706 stickyParent->updateStickyPositionConstraints(); | |
| 707 | |
| 708 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 709 | |
| 710 stickyChild->updateStickyPositionConstraints(); | |
| 711 | |
| 712 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); | |
| 713 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); | |
| 714 } | |
| 715 | |
| 716 // Verifies that the calculated position:sticky offsets are correct in the case | |
| 717 // of triple nesting. Triple (or more) nesting must be tested as the grandchild | |
| 718 // sticky must correct both its sticky box constraint rect and its containing | |
| 719 // block constaint rect. | |
| 720 TEST_F(LayoutBoxModelObjectTest, StickyPositionTripleNestedDiv) { | |
| 721 setBodyInnerHTML( | |
| 722 "<style>#scroller { height: 200px; width: 100px; overflow-y: auto; }" | |
| 723 "#prePadding { height: 50px; }" | |
| 724 "#outmostSticky { position: sticky; top: 0; height: 100px; }" | |
| 725 "#middleSticky { position: sticky; top: 0; height: 75px; }" | |
| 726 "#innerSticky { position: sticky; top: 25px; height: 25px; }" | |
| 727 "#postPadding { height: 400px }</style>" | |
| 728 "<div id='scroller'><div id='prePadding'></div><div id='outmostSticky'>" | |
| 729 "<div id='middleSticky'><div id='innerSticky'></div></div></div>" | |
| 730 "<div id='postPadding'></div></div>"); | |
| 731 | |
| 732 // Make sure that the constraints are cached before scrolling or they may be | |
| 733 // calculated post-scroll and thus not require offset correction. | |
| 734 | |
| 735 LayoutBoxModelObject* outmostSticky = | |
| 736 toLayoutBoxModelObject(getLayoutObjectByElementId("outmostSticky")); | |
| 737 outmostSticky->updateStickyPositionConstraints(); | |
| 738 | |
| 739 LayoutBoxModelObject* middleSticky = | |
| 740 toLayoutBoxModelObject(getLayoutObjectByElementId("middleSticky")); | |
| 741 middleSticky->updateStickyPositionConstraints(); | |
| 742 | |
| 743 LayoutBoxModelObject* innerSticky = | |
| 744 toLayoutBoxModelObject(getLayoutObjectByElementId("innerSticky")); | |
| 745 innerSticky->updateStickyPositionConstraints(); | |
| 746 | |
| 747 // Scroll the page down. | |
| 748 LayoutBoxModelObject* scroller = | |
| 749 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 750 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 751 scrollableArea->scrollToAbsolutePosition( | |
| 752 FloatPoint(scrollableArea->scrollPosition().x(), 100)); | |
| 753 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); | |
| 754 | |
| 755 // The grandparent and parent divs are attempting to place themselves at the | |
| 756 // top of the scrollable area. The child div is attempting to place itself at | |
| 757 // an offset of 25 pixels to the top of the scrollable area. The result of | |
| 758 // this sticky offset calculation is quite simple, but internally the child | |
| 759 // offset has to offset both its sticky box constraint rect and its containing | |
| 760 // block constraint rect. | |
| 761 EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset()); | |
| 762 EXPECT_EQ(LayoutSize(0, 0), middleSticky->stickyPositionOffset()); | |
| 763 EXPECT_EQ(LayoutSize(0, 25), innerSticky->stickyPositionOffset()); | |
| 764 | |
| 765 outmostSticky->updateStickyPositionConstraints(); | |
| 766 | |
| 767 EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset()); | |
| 768 | |
| 769 middleSticky->updateStickyPositionConstraints(); | |
| 770 | |
| 771 EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset()); | |
| 772 EXPECT_EQ(LayoutSize(0, 0), middleSticky->stickyPositionOffset()); | |
| 773 | |
| 774 innerSticky->updateStickyPositionConstraints(); | |
| 775 | |
| 776 EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset()); | |
| 777 EXPECT_EQ(LayoutSize(0, 0), middleSticky->stickyPositionOffset()); | |
| 778 EXPECT_EQ(LayoutSize(0, 25), innerSticky->stickyPositionOffset()); | |
| 779 } | |
| 780 | |
| 781 // Verifies that the calculated position:sticky offsets are correct in the case | |
| 782 // of tables. Tables are special as the containing block for table elements is | |
| 783 // always the root level <table>. | |
| 784 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedStickyTable) { | |
| 785 setBodyInnerHTML( | |
| 786 "<style>table { border-collapse: collapse; }" | |
| 787 "td, th { height: 25px; width: 25px; padding: 0; }" | |
| 788 "#scroller { height: 100px; width: 100px; overflow-y: auto; }" | |
| 789 "#prePadding { height: 50px; }" | |
| 790 "#stickyDiv { position: sticky; top: 0; height: 200px; }" | |
| 791 "#stickyTh { position: sticky; top: 0; }" | |
| 792 "#postPadding { height: 200px; }</style>" | |
| 793 "<div id='scroller'><div id='prePadding'></div><div id='stickyDiv'>" | |
| 794 "<table><thead><tr><th id='stickyTh'></th></tr></thead><tbody><tr><td>" | |
| 795 "</td></tr><tr><td></td></tr><tr><td></td></tr><tr><td></td></tr></tbody>" | |
| 796 "</table></div><div id='postPadding'></div></div>"); | |
| 797 | |
| 798 // Make sure that the constraints are cached before scrolling or they may be | |
| 799 // calculated post-scroll and thus not require offset correction. | |
| 800 | |
| 801 LayoutBoxModelObject* stickyDiv = | |
| 802 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyDiv")); | |
| 803 stickyDiv->updateStickyPositionConstraints(); | |
| 804 | |
| 805 LayoutBoxModelObject* stickyTh = | |
| 806 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh")); | |
| 807 stickyTh->updateStickyPositionConstraints(); | |
| 808 | |
| 809 // Scroll the page down. | |
| 810 LayoutBoxModelObject* scroller = | |
| 811 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 812 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 813 scrollableArea->scrollToAbsolutePosition( | |
| 814 FloatPoint(scrollableArea->scrollPosition().x(), 150)); | |
| 815 ASSERT_EQ(150.0, scrollableArea->scrollPosition().y()); | |
| 816 | |
| 817 // All sticky elements are attempting to stick to the top of the scrollable | |
| 818 // area. For the root sticky div, this requires an offset. All the other | |
| 819 // descendant sticky elements are positioned relatively so don't need offset. | |
| 820 EXPECT_EQ(LayoutSize(0, 100), stickyDiv->stickyPositionOffset()); | |
| 821 EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset()); | |
| 822 | |
| 823 // If we now scroll to the point where the overall sticky div starts to move, | |
| 824 // the table headers should continue to stick to the top of the scrollable | |
| 825 // area until they run out of <table> space to move in. | |
| 826 | |
| 827 scrollableArea->scrollToAbsolutePosition( | |
| 828 FloatPoint(scrollableArea->scrollPosition().x(), 275)); | |
| 829 ASSERT_EQ(275.0, scrollableArea->scrollPosition().y()); | |
| 830 | |
| 831 EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset()); | |
| 832 EXPECT_EQ(LayoutSize(0, 25), stickyTh->stickyPositionOffset()); | |
| 833 | |
| 834 // Finally, if we scroll so that the table is off the top of the page, the | |
| 835 // sticky header should travel as far as it can (i.e. the table height) then | |
| 836 // move off the top with it. | |
| 837 scrollableArea->scrollToAbsolutePosition( | |
| 838 FloatPoint(scrollableArea->scrollPosition().x(), 350)); | |
| 839 ASSERT_EQ(350.0, scrollableArea->scrollPosition().y()); | |
| 840 | |
| 841 EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset()); | |
| 842 EXPECT_EQ(LayoutSize(0, 100), stickyTh->stickyPositionOffset()); | |
| 843 | |
| 844 stickyDiv->updateStickyPositionConstraints(); | |
| 845 | |
| 846 EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset()); | |
| 847 | |
| 848 stickyTh->updateStickyPositionConstraints(); | |
| 849 | |
| 850 EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset()); | |
| 851 EXPECT_EQ(LayoutSize(0, 100), stickyTh->stickyPositionOffset()); | |
| 852 } | |
| 853 | |
| 854 // Verifies that the calculated position:sticky offsets are correct in the case | |
| 855 // where a particular position:sticky element is used both as a sticky-box | |
| 856 // shifting ancestor as well as a containing-block shifting ancestor. | |
| 857 // | |
| 858 // This is a rare case that can be replicated by nesting tables so that a sticky | |
| 859 // cell contains another table that has sticky elements. See the HTML below. | |
| 860 TEST_F(LayoutBoxModelObjectTest, StickyPositionComplexTableNesting) { | |
| 861 setBodyInnerHTML( | |
| 862 "<style>table { border-collapse: collapse; }" | |
| 863 "td, th { height: 25px; width: 25px; padding: 0; }" | |
| 864 "#scroller { height: 100px; width: 100px; overflow-y: auto; }" | |
| 865 "#prePadding { height: 50px; }" | |
| 866 "#outerStickyTh { height: 50px; position: sticky; top: 0; }" | |
| 867 "#innerStickyTh { position: sticky; top: 25px; }" | |
| 868 "#postPadding { height: 200px; }</style>" | |
| 869 "<div id='scroller'><div id='prePadding'></div>" | |
| 870 "<table><thead><tr><th id='outerStickyTh'><table><thead><tr>" | |
| 871 "<th id='innerStickyTh'></th></tr></thead><tbody><tr><td></td></tr>" | |
| 872 "</tbody></table></th></tr></thead><tbody><tr><td></td></tr><tr><td></td>" | |
| 873 "</tr><tr><td></td></tr><tr><td></td></tr></tbody></table>" | |
| 874 "<div id='postPadding'></div></div>"); | |
| 875 | |
| 876 // Make sure that the constraints are cached before scrolling or they may be | |
| 877 // calculated post-scroll and thus not require offset correction. | |
| 878 | |
| 879 LayoutBoxModelObject* outerStickyTh = | |
| 880 toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyTh")); | |
| 881 outerStickyTh->updateStickyPositionConstraints(); | |
| 882 | |
| 883 LayoutBoxModelObject* innerStickyTh = | |
| 884 toLayoutBoxModelObject(getLayoutObjectByElementId("innerStickyTh")); | |
| 885 innerStickyTh->updateStickyPositionConstraints(); | |
| 886 | |
| 887 // Scroll the page down. | |
| 888 LayoutBoxModelObject* scroller = | |
| 889 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 890 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 891 scrollableArea->scrollToAbsolutePosition( | |
| 892 FloatPoint(scrollableArea->scrollPosition().x(), 150)); | |
| 893 ASSERT_EQ(150.0, scrollableArea->scrollPosition().y()); | |
| 894 | |
| 895 EXPECT_EQ(LayoutSize(0, 100), outerStickyTh->stickyPositionOffset()); | |
| 896 EXPECT_EQ(LayoutSize(0, 25), innerStickyTh->stickyPositionOffset()); | |
| 897 | |
| 898 outerStickyTh->updateStickyPositionConstraints(); | |
| 899 | |
| 900 EXPECT_EQ(LayoutSize(0, 100), outerStickyTh->stickyPositionOffset()); | |
| 901 | |
| 902 innerStickyTh->updateStickyPositionConstraints(); | |
| 903 | |
| 904 EXPECT_EQ(LayoutSize(0, 100), outerStickyTh->stickyPositionOffset()); | |
| 905 EXPECT_EQ(LayoutSize(0, 25), innerStickyTh->stickyPositionOffset()); | |
| 906 } | |
| 907 | |
| 908 // Verifies that the calculated position:sticky offsets are correct in the case | |
| 909 // of nested inline elements. | |
| 910 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedInlineElements) { | |
| 911 setBodyInnerHTML( | |
| 912 "<style>#scroller { width: 100px; height: 100px; overflow-y: scroll; }" | |
| 913 "#paddingBefore { height: 50px; }" | |
| 914 "#outerInline { display: inline; position: sticky; top: 0; }" | |
| 915 "#innerInline { display: inline; position: sticky; top: 25px; }" | |
| 916 "#paddingAfter { height: 200px; }</style>" | |
| 917 "<div id='scroller'><div id='paddingBefore'></div><div id='outerInline'>" | |
| 918 "<div id='innerInline'></div></div><div id='paddingAfter'></div></div>"); | |
| 919 | |
| 920 // Make sure that the constraints are cached before scrolling or they may be | |
| 921 // calculated post-scroll and thus not require offset correction. | |
| 922 | |
| 923 LayoutBoxModelObject* outerInline = | |
| 924 toLayoutBoxModelObject(getLayoutObjectByElementId("outerInline")); | |
| 925 outerInline->updateStickyPositionConstraints(); | |
| 926 | |
| 927 LayoutBoxModelObject* innerInline = | |
| 928 toLayoutBoxModelObject(getLayoutObjectByElementId("innerInline")); | |
| 929 innerInline->updateStickyPositionConstraints(); | |
| 930 | |
| 931 // Scroll the page down. | |
| 932 LayoutBoxModelObject* scroller = | |
| 933 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 934 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 935 scrollableArea->scrollToAbsolutePosition( | |
| 936 FloatPoint(scrollableArea->scrollPosition().x(), 50)); | |
| 937 ASSERT_EQ(50.0, scrollableArea->scrollPosition().y()); | |
| 938 | |
| 939 EXPECT_EQ(LayoutSize(0, 0), outerInline->stickyPositionOffset()); | |
| 940 EXPECT_EQ(LayoutSize(0, 25), innerInline->stickyPositionOffset()); | |
| 941 | |
| 942 outerInline->updateStickyPositionConstraints(); | |
| 943 | |
| 944 EXPECT_EQ(LayoutSize(0, 0), outerInline->stickyPositionOffset()); | |
| 945 | |
| 946 innerInline->updateStickyPositionConstraints(); | |
| 947 | |
| 948 EXPECT_EQ(LayoutSize(0, 0), outerInline->stickyPositionOffset()); | |
| 949 EXPECT_EQ(LayoutSize(0, 25), innerInline->stickyPositionOffset()); | |
| 950 } | |
| 951 | |
| 952 // Verifies that the calculated position:sticky offsets are correct in the case | |
| 953 // of an intermediate position:fixed element. | |
| 954 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedFixedPos) { | |
| 955 setBodyInnerHTML( | |
| 956 "<style>body { margin: 0; }" | |
| 957 "#scroller { height: 200px; width: 100px; overflow-y: auto; }" | |
| 958 "#outerSticky { position: sticky; top: 0; height: 50px; }" | |
| 959 "#fixedDiv { position: fixed; top: 0; left: 300px; height: 100px; " | |
| 960 "width: 100px; }" | |
| 961 "#innerSticky { position: sticky; top: 25px; height: 25px; }" | |
| 962 "#padding { height: 400px }</style>" | |
| 963 "<div id='scroller'><div id='outerSticky'><div id='fixedDiv'>" | |
| 964 "<div id='innerSticky'></div></div></div><div id='padding'></div></div>"); | |
| 965 | |
| 966 // Make sure that the constraints are cached before scrolling or they may be | |
| 967 // calculated post-scroll and thus not require offset correction. | |
| 968 | |
| 969 LayoutBoxModelObject* outerSticky = | |
| 970 toLayoutBoxModelObject(getLayoutObjectByElementId("outerSticky")); | |
| 971 outerSticky->updateStickyPositionConstraints(); | |
| 972 | |
| 973 LayoutBoxModelObject* innerSticky = | |
| 974 toLayoutBoxModelObject(getLayoutObjectByElementId("innerSticky")); | |
| 975 innerSticky->updateStickyPositionConstraints(); | |
| 976 | |
| 977 LayoutBoxModelObject* scroller = | |
| 978 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); | |
| 979 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); | |
| 980 | |
| 981 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); | |
| 982 ASSERT_TRUE(constraintsMap.contains(outerSticky->layer())); | |
| 983 ASSERT_TRUE(constraintsMap.contains(innerSticky->layer())); | |
| 984 | |
| 985 // The inner sticky should not detect the outer one as any sort of ancestor. | |
| 986 EXPECT_FALSE(constraintsMap.get(innerSticky->layer()) | |
| 987 .nearestStickyElementShiftingStickyBox()); | |
| 988 EXPECT_FALSE(constraintsMap.get(innerSticky->layer()) | |
| 989 .nearestStickyElementShiftingContainingBlock()); | |
| 990 | |
| 991 // Scroll the page down. | |
| 992 scrollableArea->scrollToAbsolutePosition( | |
| 993 FloatPoint(scrollableArea->scrollPosition().x(), 100)); | |
| 994 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); | |
| 995 | |
| 996 // TODO(smcgruer): Until http://crbug.com/686164 is fixed, we need to update | |
| 997 // the constraints here before calculations will be correct. | |
| 998 innerSticky->updateStickyPositionConstraints(); | |
| 999 | |
| 1000 EXPECT_EQ(LayoutSize(0, 100), outerSticky->stickyPositionOffset()); | |
| 1001 EXPECT_EQ(LayoutSize(0, 25), innerSticky->stickyPositionOffset()); | |
| 1002 } | |
| 1003 | |
| 331 } // namespace blink | 1004 } // namespace blink |
| OLD | NEW |