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

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

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

Powered by Google App Engine
This is Rietveld 408576698