| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/layout/LayoutBoxModelObject.h" | 5 #include "core/layout/LayoutBoxModelObject.h" |
| 6 | 6 |
| 7 #include "core/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 231 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 242 | 242 |
| 243 const StickyPositionScrollingConstraints& constraints = | 243 const StickyPositionScrollingConstraints& constraints = |
| 244 scrollableArea->stickyConstraintsMap().get(sticky->layer()); | 244 scrollableArea->stickyConstraintsMap().get(sticky->layer()); |
| 245 ASSERT_EQ(IntRect(15, 115, 170, 370), | 245 ASSERT_EQ(IntRect(15, 115, 170, 370), |
| 246 enclosingIntRect( | 246 enclosingIntRect( |
| 247 getScrollContainerRelativeContainingBlockRect(constraints))); | 247 getScrollContainerRelativeContainingBlockRect(constraints))); |
| 248 ASSERT_EQ( | 248 ASSERT_EQ( |
| 249 IntRect(15, 165, 100, 100), | 249 IntRect(15, 165, 100, 100), |
| 250 enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints))); | 250 enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints))); |
| 251 } | 251 } |
| 252 |
| 253 // Verifies that the correct "to containing block" sticky ancestor is found when |
| 254 // computing the sticky constraints. |
| 255 // |
| 256 // In most cases, this pointer should be null since your parent() is normally |
| 257 // your containingBlock. However there are a few cases where this is not true, |
| 258 // most notably in tables where the root <table> is the containingBlock for many |
| 259 // of the descendant elements. |
| 260 TEST_F(LayoutBoxModelObjectTest, |
| 261 StickyPositionFindsCorrectToContainingBlockStickyAncestor) { |
| 262 setBodyInnerHTML( |
| 263 "<style>#stickyOuterDiv { position: sticky; }" |
| 264 "#stickyInnerDiv { position: sticky; }" |
| 265 "#stickyThead { position: sticky; }" |
| 266 "#stickyTr { position: sticky; }" |
| 267 "#stickyTh { position: sticky; }</style>" |
| 268 "<div id='stickyOuterDiv'><div id='stickyInnerDiv'></div><table>" |
| 269 "<thead id='stickyThead'><tr id='stickyTr'><th id='stickyTh'></th></tr>" |
| 270 "</thead></table></div>"); |
| 271 |
| 272 // Force the constraints to be calculated. |
| 273 LayoutBoxModelObject* stickyOuterDiv = |
| 274 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuterDiv")); |
| 275 stickyOuterDiv->updateStickyPositionConstraints(); |
| 276 |
| 277 LayoutBoxModelObject* stickyInnerDiv = |
| 278 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyInnerDiv")); |
| 279 stickyInnerDiv->updateStickyPositionConstraints(); |
| 280 |
| 281 LayoutBoxModelObject* stickyThead = |
| 282 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyThead")); |
| 283 stickyThead->updateStickyPositionConstraints(); |
| 284 |
| 285 LayoutBoxModelObject* stickyTr = |
| 286 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTr")); |
| 287 stickyTr->updateStickyPositionConstraints(); |
| 288 |
| 289 LayoutBoxModelObject* stickyTh = |
| 290 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh")); |
| 291 stickyTh->updateStickyPositionConstraints(); |
| 292 |
| 293 // There's only one scrollableArea, the main page. |
| 294 PaintLayerScrollableArea* scrollableArea = |
| 295 stickyOuterDiv->layer()->ancestorOverflowLayer()->getScrollableArea(); |
| 296 ASSERT_TRUE(scrollableArea); |
| 297 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); |
| 298 |
| 299 ASSERT_TRUE(constraintsMap.contains(stickyOuterDiv->layer())); |
| 300 ASSERT_TRUE(constraintsMap.contains(stickyInnerDiv->layer())); |
| 301 ASSERT_TRUE(constraintsMap.contains(stickyThead->layer())); |
| 302 ASSERT_TRUE(constraintsMap.contains(stickyTr->layer())); |
| 303 ASSERT_TRUE(constraintsMap.contains(stickyTh->layer())); |
| 304 |
| 305 // The outer <div> trivially has no ancestor. |
| 306 EXPECT_FALSE(constraintsMap.get(stickyOuterDiv->layer()) |
| 307 .nearestStickyElementToContainingBlock()); |
| 308 |
| 309 // Neither does the inner <div>, as the "to containing block" range is |
| 310 // exclusive of the containing block. |
| 311 EXPECT_FALSE(constraintsMap.get(stickyInnerDiv->layer()) |
| 312 .nearestStickyElementToContainingBlock()); |
| 313 |
| 314 // All of the table elements containing block is the <table> itself, so the |
| 315 // <tr> and <th> both have ancestors. |
| 316 EXPECT_FALSE(constraintsMap.get(stickyThead->layer()) |
| 317 .nearestStickyElementToContainingBlock()); |
| 318 EXPECT_EQ(stickyThead, constraintsMap.get(stickyTr->layer()) |
| 319 .nearestStickyElementToContainingBlock()); |
| 320 EXPECT_EQ(stickyTr, constraintsMap.get(stickyTh->layer()) |
| 321 .nearestStickyElementToContainingBlock()); |
| 322 } |
| 323 |
| 324 // Verifies that the correct "to scroll container" sticky ancestor is found when |
| 325 // computing the sticky constraints. |
| 326 // |
| 327 // The "to scroll container" ancestor is the nearest sticky ancestor between our |
| 328 // containing block (inclusive) and our scroll container (exclusive). |
| 329 TEST_F(LayoutBoxModelObjectTest, |
| 330 StickyPositionFindsCorrectToScrollContainerStickyAncestor) { |
| 331 // We make the scroller itself sticky in order to check that elements do not |
| 332 // detect it as their sticky ancestor. |
| 333 setBodyInnerHTML( |
| 334 "<style>#scroller { overflow-y: scroll; position: sticky; }" |
| 335 "#stickyParent { position: sticky; }" |
| 336 "#stickyChild { position: sticky; }" |
| 337 "#stickyNestedChild { position: sticky; }</style>" |
| 338 "<div id='scroller'><div id='stickyParent'><div id='stickyChild'></div>" |
| 339 "<div><div id='stickyNestedChild'></div></div></div></div>"); |
| 340 |
| 341 // Force the constraints to be calculated. |
| 342 LayoutBoxModelObject* scroller = |
| 343 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 344 scroller->updateStickyPositionConstraints(); |
| 345 |
| 346 LayoutBoxModelObject* stickyParent = |
| 347 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 348 stickyParent->updateStickyPositionConstraints(); |
| 349 |
| 350 LayoutBoxModelObject* stickyChild = |
| 351 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 352 stickyChild->updateStickyPositionConstraints(); |
| 353 |
| 354 LayoutBoxModelObject* stickyNestedChild = |
| 355 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyNestedChild")); |
| 356 stickyNestedChild->updateStickyPositionConstraints(); |
| 357 |
| 358 // There are two scrollable areas, but we only care about the scroller itself. |
| 359 PaintLayerScrollableArea* scrollableArea = |
| 360 scroller->layer()->getScrollableArea(); |
| 361 ASSERT_TRUE(scrollableArea); |
| 362 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); |
| 363 |
| 364 ASSERT_FALSE(constraintsMap.contains(scroller->layer())); |
| 365 ASSERT_TRUE(constraintsMap.contains(stickyParent->layer())); |
| 366 ASSERT_TRUE(constraintsMap.contains(stickyChild->layer())); |
| 367 ASSERT_TRUE(constraintsMap.contains(stickyNestedChild->layer())); |
| 368 |
| 369 // The outer <div> should not count the scroller as its ancestor. |
| 370 EXPECT_FALSE(constraintsMap.get(stickyParent->layer()) |
| 371 .nearestStickyElementFromContainingBlockToScrollContainer()); |
| 372 |
| 373 // Both inner children should count the parent <div> as their ancestor. |
| 374 EXPECT_EQ(stickyParent, |
| 375 constraintsMap.get(stickyChild->layer()) |
| 376 .nearestStickyElementFromContainingBlockToScrollContainer()); |
| 377 EXPECT_EQ(stickyParent, |
| 378 constraintsMap.get(stickyNestedChild->layer()) |
| 379 .nearestStickyElementFromContainingBlockToScrollContainer()); |
| 380 } |
| 381 |
| 382 // Verifies that the correct "to scroll container" sticky ancestor is found when |
| 383 // computing the sticky constraints in the case of tables. |
| 384 // |
| 385 // Tables are unusual because the containingBlock for all table elements is the |
| 386 // <table> itself, so we have to skip over elements to find the "to scroll |
| 387 // container" stick ancestor. |
| 388 TEST_F(LayoutBoxModelObjectTest, |
| 389 StickyPositionFindsCorrectToScrollContainerStickyAncestorTable) { |
| 390 setBodyInnerHTML( |
| 391 "<style>#scroller { overflow-y: scroll; }" |
| 392 "#stickyOuter { position: sticky; }" |
| 393 "#stickyThead { position: sticky; }" |
| 394 "#stickyTr { position: sticky; }" |
| 395 "#stickyTh { position: sticky; }</style>" |
| 396 "<div id='scroller'><div id='stickyOuter'><table><thead id='stickyThead'>" |
| 397 "<tr id='stickyTr'><th id='stickyTh'></th></tr></thead></table></div>" |
| 398 "</div>"); |
| 399 |
| 400 // Force the constraints to be calculated. |
| 401 LayoutBoxModelObject* scroller = |
| 402 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 403 scroller->updateStickyPositionConstraints(); |
| 404 |
| 405 LayoutBoxModelObject* stickyOuter = |
| 406 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyOuter")); |
| 407 stickyOuter->updateStickyPositionConstraints(); |
| 408 |
| 409 LayoutBoxModelObject* stickyThead = |
| 410 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyThead")); |
| 411 stickyThead->updateStickyPositionConstraints(); |
| 412 |
| 413 LayoutBoxModelObject* stickyTr = |
| 414 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTr")); |
| 415 stickyTr->updateStickyPositionConstraints(); |
| 416 |
| 417 LayoutBoxModelObject* stickyTh = |
| 418 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh")); |
| 419 stickyTh->updateStickyPositionConstraints(); |
| 420 |
| 421 // There are two scrollable areas, but we only care about the scroller itself. |
| 422 PaintLayerScrollableArea* scrollableArea = |
| 423 scroller->layer()->getScrollableArea(); |
| 424 ASSERT_TRUE(scrollableArea); |
| 425 StickyConstraintsMap constraintsMap = scrollableArea->stickyConstraintsMap(); |
| 426 |
| 427 ASSERT_FALSE(constraintsMap.contains(scroller->layer())); |
| 428 ASSERT_TRUE(constraintsMap.contains(stickyOuter->layer())); |
| 429 ASSERT_TRUE(constraintsMap.contains(stickyThead->layer())); |
| 430 ASSERT_TRUE(constraintsMap.contains(stickyTr->layer())); |
| 431 ASSERT_TRUE(constraintsMap.contains(stickyTh->layer())); |
| 432 |
| 433 // All of the table elements should point to the outer <div> as their |
| 434 // "to scroll container" sticky ancestor. |
| 435 EXPECT_EQ(stickyOuter, |
| 436 constraintsMap.get(stickyThead->layer()) |
| 437 .nearestStickyElementFromContainingBlockToScrollContainer()); |
| 438 EXPECT_EQ(stickyOuter, |
| 439 constraintsMap.get(stickyTr->layer()) |
| 440 .nearestStickyElementFromContainingBlockToScrollContainer()); |
| 441 EXPECT_EQ(stickyOuter, |
| 442 constraintsMap.get(stickyTh->layer()) |
| 443 .nearestStickyElementFromContainingBlockToScrollContainer()); |
| 444 } |
| 445 |
| 446 // Verifies that the calculated position:sticky offsets are correct when we have |
| 447 // a simple case of nested sticky elements. |
| 448 TEST_F(LayoutBoxModelObjectTest, StickyPositionNested) { |
| 449 setBodyInnerHTML( |
| 450 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 451 "#prePadding { height: 50px }" |
| 452 "#stickyParent { position: sticky; top: 0; height: 50px; }" |
| 453 "#stickyChild { position: sticky; top: 0; height: 25px; }" |
| 454 "#postPadding { height: 200px }</style>" |
| 455 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 456 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 457 |
| 458 // Make sure that the constraints are cached before scrolling or they may be |
| 459 // calculated post-scroll and thus not require offset correction. |
| 460 |
| 461 LayoutBoxModelObject* stickyParent = |
| 462 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 463 stickyParent->updateStickyPositionConstraints(); |
| 464 |
| 465 LayoutBoxModelObject* stickyChild = |
| 466 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 467 stickyChild->updateStickyPositionConstraints(); |
| 468 |
| 469 // Scroll the page down. |
| 470 LayoutBoxModelObject* scroller = |
| 471 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 472 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 473 scrollableArea->scrollToAbsolutePosition( |
| 474 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 475 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 476 |
| 477 // Both the parent and child sticky divs are attempting to place themselves at |
| 478 // the top of the scrollable area. To achieve this the parent must offset on |
| 479 // the y-axis against its starting position. The child is offset relative to |
| 480 // its parent so should not move at all. |
| 481 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); |
| 482 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); |
| 483 } |
| 484 |
| 485 // Verifies that the calculated position:sticky offsets are correct when the |
| 486 // child has a larger edge constraint value than the parent. |
| 487 TEST_F(LayoutBoxModelObjectTest, StickyPositionChildHasLargerTop) { |
| 488 setBodyInnerHTML( |
| 489 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 490 "#prePadding { height: 50px }" |
| 491 "#stickyParent { position: sticky; top: 0; height: 50px; }" |
| 492 "#stickyChild { position: sticky; top: 25px; height: 25px; }" |
| 493 "#postPadding { height: 200px }</style>" |
| 494 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 495 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 496 |
| 497 // Make sure that the constraints are cached before scrolling or they may be |
| 498 // calculated post-scroll and thus not require offset correction. |
| 499 |
| 500 LayoutBoxModelObject* stickyParent = |
| 501 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 502 stickyParent->updateStickyPositionConstraints(); |
| 503 |
| 504 LayoutBoxModelObject* stickyChild = |
| 505 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 506 stickyChild->updateStickyPositionConstraints(); |
| 507 |
| 508 // Scroll the page down. |
| 509 LayoutBoxModelObject* scroller = |
| 510 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 511 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 512 scrollableArea->scrollToAbsolutePosition( |
| 513 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 514 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 515 |
| 516 // The parent is attempting to place itself at the top of the scrollable area, |
| 517 // whilst the child is attempting to be 25 pixels from the top. To achieve |
| 518 // this both must offset on the y-axis against their starting positions, but |
| 519 // note the child is offset relative to the parent. |
| 520 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); |
| 521 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); |
| 522 } |
| 523 |
| 524 // Verifies that the calculated position:sticky offsets are correct when the |
| 525 // child has a smaller edge constraint value than the parent. |
| 526 TEST_F(LayoutBoxModelObjectTest, StickyPositionParentHasLargerTop) { |
| 527 setBodyInnerHTML( |
| 528 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 529 "#prePadding { height: 50px }" |
| 530 "#stickyParent { position: sticky; top: 25px; height: 50px; }" |
| 531 "#stickyChild { position: sticky; top: 0; height: 25px; }" |
| 532 "#postPadding { height: 200px }</style>" |
| 533 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 534 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 535 |
| 536 // Make sure that the constraints are cached before scrolling or they may be |
| 537 // calculated post-scroll and thus not require offset correction. |
| 538 |
| 539 LayoutBoxModelObject* stickyParent = |
| 540 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 541 stickyParent->updateStickyPositionConstraints(); |
| 542 |
| 543 LayoutBoxModelObject* stickyChild = |
| 544 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 545 stickyChild->updateStickyPositionConstraints(); |
| 546 |
| 547 // Scroll the page down. |
| 548 LayoutBoxModelObject* scroller = |
| 549 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 550 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 551 scrollableArea->scrollToAbsolutePosition( |
| 552 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 553 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 554 |
| 555 // The parent is attempting to place itself 25 pixels from the top of the |
| 556 // scrollable area, whilst the child is attempting to be at the top. However, |
| 557 // the child must stay contained within the parent, so it should be pushed |
| 558 // down to the same height. As always, the child offset is relative. |
| 559 EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset()); |
| 560 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); |
| 561 } |
| 562 |
| 563 // Verifies that the calculated position:sticky offsets are correct when the |
| 564 // child has a large enough edge constraint value to push outside of its parent. |
| 565 TEST_F(LayoutBoxModelObjectTest, StickyPositionChildPushingOutsideParent) { |
| 566 setBodyInnerHTML( |
| 567 "<style> #scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 568 "#prePadding { height: 50px; }" |
| 569 "#stickyParent { position: sticky; top: 0; height: 50px; }" |
| 570 "#stickyChild { position: sticky; top: 50px; height: 25px; }" |
| 571 "#postPadding { height: 200px }</style>" |
| 572 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 573 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 574 |
| 575 // Make sure that the constraints are cached before scrolling or they may be |
| 576 // calculated post-scroll and thus not require offset correction. |
| 577 |
| 578 LayoutBoxModelObject* stickyParent = |
| 579 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 580 stickyParent->updateStickyPositionConstraints(); |
| 581 |
| 582 LayoutBoxModelObject* stickyChild = |
| 583 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 584 stickyChild->updateStickyPositionConstraints(); |
| 585 |
| 586 // Scroll the page down. |
| 587 LayoutBoxModelObject* scroller = |
| 588 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 589 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 590 scrollableArea->scrollToAbsolutePosition( |
| 591 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 592 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 593 |
| 594 // The parent is attempting to place itself at the top of the scrollable area, |
| 595 // whilst the child is attempting to be 50 pixels from the top. However, there |
| 596 // is only 25 pixels of space for the child to move into, so it should be |
| 597 // capped by that offset. As always, the child offset is relative. |
| 598 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); |
| 599 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); |
| 600 } |
| 601 |
| 602 // Verifies that the calculated position:sticky offsets are correct in the case |
| 603 // of triple nesting. Triple (or more) nesting must be tested as the grandchild |
| 604 // sticky must be corrected for both the containing block and the viewport. |
| 605 TEST_F(LayoutBoxModelObjectTest, StickyPositionTripleNestedDiv) { |
| 606 setBodyInnerHTML( |
| 607 "<style>#scroller { height: 200px; width: 100px; overflow-y: auto; }" |
| 608 "#prePadding { height: 50px; }" |
| 609 "#outmostSticky { position: sticky; top: 0; height: 100px; }" |
| 610 "#middleSticky { position: sticky; top: 0; height: 75px; }" |
| 611 "#innerSticky { position: sticky; top: 25px; height: 25px; }" |
| 612 "#postPadding { height: 400px }</style>" |
| 613 "<div id='scroller'><div id='prePadding'></div><div id='outmostSticky'>" |
| 614 "<div id='middleSticky'><div id='innerSticky'></div></div></div>" |
| 615 "<div id='postPadding'></div></div>"); |
| 616 |
| 617 // Make sure that the constraints are cached before scrolling or they may be |
| 618 // calculated post-scroll and thus not require offset correction. |
| 619 |
| 620 LayoutBoxModelObject* outmostSticky = |
| 621 toLayoutBoxModelObject(getLayoutObjectByElementId("outmostSticky")); |
| 622 outmostSticky->updateStickyPositionConstraints(); |
| 623 |
| 624 LayoutBoxModelObject* middleSticky = |
| 625 toLayoutBoxModelObject(getLayoutObjectByElementId("middleSticky")); |
| 626 middleSticky->updateStickyPositionConstraints(); |
| 627 |
| 628 LayoutBoxModelObject* innerSticky = |
| 629 toLayoutBoxModelObject(getLayoutObjectByElementId("innerSticky")); |
| 630 innerSticky->updateStickyPositionConstraints(); |
| 631 |
| 632 // Scroll the page down. |
| 633 LayoutBoxModelObject* scroller = |
| 634 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 635 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 636 scrollableArea->scrollToAbsolutePosition( |
| 637 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 638 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 639 |
| 640 // The grandparent and parent divs are attempting to place themselves at the |
| 641 // top of the scrollable area. The child div is attempting to place itself at |
| 642 // an offset of 25 pixels to the top of the scrollable area. The result of |
| 643 // this sticky offset calculation is quite simple, but internally the child |
| 644 // offset has to offset both against its containing block and against its |
| 645 // viewport ancestor (the grandparent). |
| 646 EXPECT_EQ(LayoutSize(0, 50), outmostSticky->stickyPositionOffset()); |
| 647 EXPECT_EQ(LayoutSize(0, 0), middleSticky->stickyPositionOffset()); |
| 648 EXPECT_EQ(LayoutSize(0, 25), innerSticky->stickyPositionOffset()); |
| 649 } |
| 650 |
| 651 // Verifies that the calculated position:sticky offsets are correct in the case |
| 652 // of tables. Tables are special as the containing block for all child elements |
| 653 // is always the root level <table>. |
| 654 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedStickyInTable) { |
| 655 setBodyInnerHTML( |
| 656 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 657 "#prePadding { height: 50px; }" |
| 658 "#stickyDiv { position: sticky; top: 0; height: 200px; }" |
| 659 "#table { border-collapse: collapse; }" |
| 660 "#stickyThead { position: sticky; top: 0; }" |
| 661 "#stickyTr { position: sticky; top: 0; }" |
| 662 "#stickyTh { position: sticky; top: 0; }" |
| 663 "#postPadding { height: 200px; }</style>" |
| 664 "<div id='scroller'><div id='prePadding'></div><div id='stickyDiv'>" |
| 665 "<table id='table'><thead id='stickyThead'><tr id='stickyTr'>" |
| 666 "<th id='stickyTh'>Header</th></tr></thread><tbody><tr><td>0</td></tr>" |
| 667 "<tr><td>1</td></tr><tr><td>2</td></tr><tr><td>3</td></tr></tbody>" |
| 668 "</table></div><div id='postPadding'></div></div>"); |
| 669 |
| 670 // Make sure that the constraints are cached before scrolling or they may be |
| 671 // calculated post-scroll and thus not require offset correction. |
| 672 |
| 673 LayoutBoxModelObject* stickyDiv = |
| 674 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyDiv")); |
| 675 stickyDiv->updateStickyPositionConstraints(); |
| 676 |
| 677 LayoutBoxModelObject* stickyThead = |
| 678 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyThead")); |
| 679 stickyThead->updateStickyPositionConstraints(); |
| 680 |
| 681 LayoutBoxModelObject* stickyTr = |
| 682 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTr")); |
| 683 stickyTr->updateStickyPositionConstraints(); |
| 684 |
| 685 LayoutBoxModelObject* stickyTh = |
| 686 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh")); |
| 687 stickyTh->updateStickyPositionConstraints(); |
| 688 |
| 689 // Scroll the page down. |
| 690 LayoutBoxModelObject* scroller = |
| 691 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 692 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 693 scrollableArea->scrollToAbsolutePosition( |
| 694 FloatPoint(scrollableArea->scrollPosition().x(), 150)); |
| 695 ASSERT_EQ(150.0, scrollableArea->scrollPosition().y()); |
| 696 |
| 697 // All sticky elements are attempting to stick to the top of the scrollable |
| 698 // area. For the root sticky div, this requires an offset. All the other |
| 699 // descendant sticky elements are positioned relatively so don't need offset. |
| 700 EXPECT_EQ(LayoutSize(0, 100), stickyDiv->stickyPositionOffset()); |
| 701 EXPECT_EQ(LayoutSize(0, 0), stickyThead->stickyPositionOffset()); |
| 702 EXPECT_EQ(LayoutSize(0, 0), stickyTr->stickyPositionOffset()); |
| 703 EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset()); |
| 704 |
| 705 // If we now scroll to the point where the overall sticky div starts to move, |
| 706 // the table headers should continue to stick to the top of the scrollable |
| 707 // area until they run out of <table> space to move in. |
| 708 |
| 709 scrollableArea->scrollToAbsolutePosition( |
| 710 FloatPoint(scrollableArea->scrollPosition().x(), 275)); |
| 711 ASSERT_EQ(275.0, scrollableArea->scrollPosition().y()); |
| 712 |
| 713 // TODO(smcgruer): I'm not sure that 12 is correct. My hankerchief math gets |
| 714 // me something like (275 - 50 - 200) = 25? But it renders correctly. |
| 715 EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset()); |
| 716 EXPECT_EQ(LayoutSize(0, 12), stickyThead->stickyPositionOffset()); |
| 717 EXPECT_EQ(LayoutSize(0, 0), stickyTr->stickyPositionOffset()); |
| 718 EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset()); |
| 719 |
| 720 // Finally, if we scroll so that the table is off the top of the page, the |
| 721 // sticky header should go off the top with it. |
| 722 scrollableArea->scrollToAbsolutePosition( |
| 723 FloatPoint(scrollableArea->scrollPosition().x(), 350)); |
| 724 ASSERT_EQ(350.0, scrollableArea->scrollPosition().y()); |
| 725 |
| 726 // TODO(smcgruer): Again I have no idea why this is correct. But it does |
| 727 // render correctly. |
| 728 EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset()); |
| 729 EXPECT_EQ(LayoutSize(0, 12), stickyThead->stickyPositionOffset()); |
| 730 EXPECT_EQ(LayoutSize(0, 0), stickyTr->stickyPositionOffset()); |
| 731 EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset()); |
| 732 } |
| 733 |
| 734 // Verifies that the calculated position:sticky offsets are correct in the case |
| 735 // where a particular position:sticky element is used both as a "to containing |
| 736 // block" ancestor as well as a "to scroll container" ancestor. |
| 737 // |
| 738 // This is a rare case that can be replicated by nesting tables so that a sticky |
| 739 // cell contains another table that has sticky elements. See the HTML below. |
| 740 TEST_F(LayoutBoxModelObjectTest, StickyPositionComplexTableNesting) { |
| 741 setBodyInnerHTML( |
| 742 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 743 "#prePadding { height: 50px; }" |
| 744 "#outerStickyThead { position: sticky; top: 0; }" |
| 745 "#outerStickyTr { position: sticky; top: 0; }" |
| 746 "#outerStickyTh { position: sticky; top: 0; }" |
| 747 "#innerStickyThead { position: sticky; top: 25px; }" |
| 748 "#innerStickyTr { position: sticky; top: 25px; }" |
| 749 "#innerStickyTh { position: sticky; top: 25px; }" |
| 750 "#postPadding { height: 200px; }" |
| 751 "table { border-collapse: collapse; }</style>" |
| 752 "<div id='scroller'><div id='prePadding'></div>" |
| 753 "<table><thead id='outerStickyThead'><tr id='outerStickyTr'>" |
| 754 "<th id='outerStickyTh'><table><thead id='innerStickyThead'>" |
| 755 "<tr id='innerStickyTr'><th id='innerStickyTh'>Nested</th></tr></thead>" |
| 756 "</table></th></tr></thread><tbody><tr><td>0</td></tr><tr><td>1</td></tr>" |
| 757 "<tr><td>2</td></tr><tr><td>3</td></tr></tbody></table>" |
| 758 "<div id='postPadding'></div></div>"); |
| 759 |
| 760 // Make sure that the constraints are cached before scrolling or they may be |
| 761 // calculated post-scroll and thus not require offset correction. |
| 762 |
| 763 LayoutBoxModelObject* outerStickyThead = |
| 764 toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyThead")); |
| 765 outerStickyThead->updateStickyPositionConstraints(); |
| 766 |
| 767 LayoutBoxModelObject* outerStickyTr = |
| 768 toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyTr")); |
| 769 outerStickyTr->updateStickyPositionConstraints(); |
| 770 |
| 771 LayoutBoxModelObject* outerStickyTh = |
| 772 toLayoutBoxModelObject(getLayoutObjectByElementId("outerStickyTh")); |
| 773 outerStickyTh->updateStickyPositionConstraints(); |
| 774 |
| 775 LayoutBoxModelObject* innerStickyThead = |
| 776 toLayoutBoxModelObject(getLayoutObjectByElementId("innerStickyThead")); |
| 777 innerStickyThead->updateStickyPositionConstraints(); |
| 778 |
| 779 LayoutBoxModelObject* innerStickyTr = |
| 780 toLayoutBoxModelObject(getLayoutObjectByElementId("innerStickyTr")); |
| 781 innerStickyTr->updateStickyPositionConstraints(); |
| 782 |
| 783 LayoutBoxModelObject* innerStickyTh = |
| 784 toLayoutBoxModelObject(getLayoutObjectByElementId("innerStickyTh")); |
| 785 innerStickyTh->updateStickyPositionConstraints(); |
| 786 |
| 787 // Scroll the page down. |
| 788 LayoutBoxModelObject* scroller = |
| 789 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 790 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 791 scrollableArea->scrollToAbsolutePosition( |
| 792 FloatPoint(scrollableArea->scrollPosition().x(), 150)); |
| 793 ASSERT_EQ(150.0, scrollableArea->scrollPosition().y()); |
| 794 |
| 795 // TODO(smcgruer): I have no idea again where these numbers come from, but it |
| 796 // renders correctly! |
| 797 EXPECT_EQ(LayoutSize(0, 12), outerStickyThead->stickyPositionOffset()); |
| 798 EXPECT_EQ(LayoutSize(0, 0), outerStickyTr->stickyPositionOffset()); |
| 799 EXPECT_EQ(LayoutSize(0, 0), outerStickyTh->stickyPositionOffset()); |
| 800 EXPECT_EQ(LayoutSize(0, 0), innerStickyThead->stickyPositionOffset()); |
| 801 EXPECT_EQ(LayoutSize(0, 0), innerStickyTr->stickyPositionOffset()); |
| 802 EXPECT_EQ(LayoutSize(0, 0), innerStickyTh->stickyPositionOffset()); |
| 803 |
| 804 // TODO(smcgruer): More scrolling. |
| 805 } |
| 806 |
| 252 } // namespace blink | 807 } // namespace blink |
| OLD | NEW |