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

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

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

Powered by Google App Engine
This is Rietveld 408576698