Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp

Issue 2708883005: Handle nested position:sticky elements correctly (main thread) (Closed)
Patch Set: Rebase Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698