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

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

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

Powered by Google App Engine
This is Rietveld 408576698