| 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 sticky constraints are correct when we have a simple case |
| 254 // of nested position:sticky. |
| 255 TEST_F(LayoutBoxModelObjectTest, StickyPositionNested) { |
| 256 setBodyInnerHTML( |
| 257 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 258 "#prePadding { height: 50px }" |
| 259 "#stickyParent { position: sticky; top: 0; height: 50px; }" |
| 260 "#stickyChild { position: sticky; top: 0; height: 25px; }" |
| 261 "#postPadding { height: 200px }</style>" |
| 262 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 263 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 264 |
| 265 // Make sure that the constraints are cached before scrolling, or they will |
| 266 // simply be calculated post-scroll and thus not require offset correction. |
| 267 |
| 268 LayoutBoxModelObject* stickyParent = |
| 269 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 270 stickyParent->updateStickyPositionConstraints(); |
| 271 |
| 272 LayoutBoxModelObject* stickyChild = |
| 273 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 274 stickyChild->updateStickyPositionConstraints(); |
| 275 |
| 276 // Scroll the page down. |
| 277 LayoutBoxModelObject* scroller = |
| 278 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 279 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 280 scrollableArea->scrollToAbsolutePosition( |
| 281 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 282 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 283 |
| 284 // Both the parent and child sticky divs are attempting to place themselves at |
| 285 // the top of the scrollable area. To achieve this the parent must offset on |
| 286 // the y-axis against its starting position. The child is offset relative to |
| 287 // its parent so the child should not move at all. |
| 288 |
| 289 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); |
| 290 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); |
| 291 } |
| 292 |
| 293 // Verifies that the sticky constraints are correct when the child has a |
| 294 // larger edge constraint value than the parent. |
| 295 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedChildLargerTop) { |
| 296 setBodyInnerHTML( |
| 297 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 298 "#prePadding { height: 50px }" |
| 299 "#stickyParent { position: sticky; top: 0; height: 50px; }" |
| 300 "#stickyChild { position: sticky; top: 25px; height: 25px; }" |
| 301 "#postPadding { height: 200px }</style>" |
| 302 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 303 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 304 |
| 305 // Make sure that the constraints are cached before scrolling, or they will |
| 306 // simply be calculated post-scroll and thus not require offset correction. |
| 307 |
| 308 LayoutBoxModelObject* stickyParent = |
| 309 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 310 stickyParent->updateStickyPositionConstraints(); |
| 311 |
| 312 LayoutBoxModelObject* stickyChild = |
| 313 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 314 stickyChild->updateStickyPositionConstraints(); |
| 315 |
| 316 // Scroll the page down. |
| 317 LayoutBoxModelObject* scroller = |
| 318 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 319 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 320 scrollableArea->scrollToAbsolutePosition( |
| 321 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 322 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 323 |
| 324 // The parent is attempting to place itself at the top of the scrollable area, |
| 325 // whilst the child is attempting to be 25 pixels from the top. To achieve |
| 326 // this both must offset on the y-axis against their starting positions, but |
| 327 // note the child is offset relative to the parent. |
| 328 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); |
| 329 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); |
| 330 } |
| 331 |
| 332 // Verifies that the sticky constraints are correct when the child has a |
| 333 // smaller edge constraint value than the parent. |
| 334 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedParentLargerTop) { |
| 335 setBodyInnerHTML( |
| 336 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 337 "#prePadding { height: 50px }" |
| 338 "#stickyParent { position: sticky; top: 25px; height: 50px; }" |
| 339 "#stickyChild { position: sticky; top: 0; height: 25px; }" |
| 340 "#postPadding { height: 200px }</style>" |
| 341 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 342 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 343 |
| 344 // Make sure that the constraints are cached before scrolling, or they will |
| 345 // simply be calculated post-scroll and thus not require offset correction. |
| 346 |
| 347 LayoutBoxModelObject* stickyParent = |
| 348 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 349 stickyParent->updateStickyPositionConstraints(); |
| 350 |
| 351 LayoutBoxModelObject* stickyChild = |
| 352 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 353 stickyChild->updateStickyPositionConstraints(); |
| 354 |
| 355 // Scroll the page down. |
| 356 LayoutBoxModelObject* scroller = |
| 357 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 358 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 359 scrollableArea->scrollToAbsolutePosition( |
| 360 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 361 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 362 |
| 363 // The parent is attempting to place itself 25 pixels from the top of the |
| 364 // scrollable area, whilst the child is attempting to be at the top. However, |
| 365 // the child must stay contained within the parent, so it should be pushed |
| 366 // down to the same height. As always, the child offset is relative. |
| 367 EXPECT_EQ(LayoutSize(0, 75), stickyParent->stickyPositionOffset()); |
| 368 EXPECT_EQ(LayoutSize(0, 0), stickyChild->stickyPositionOffset()); |
| 369 } |
| 370 |
| 371 // Verifies that the sticky constraints are correct when the child has a large |
| 372 // enough edge constraint value to try and push outside of its parent. |
| 373 TEST_F(LayoutBoxModelObjectTest, |
| 374 StickyPositionNestedChildPushingOutsideParent) { |
| 375 setBodyInnerHTML( |
| 376 "<style> #scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 377 "#prePadding { height: 50px; }" |
| 378 "#stickyParent { position: sticky; top: 0; height: 50px; }" |
| 379 "#stickyChild { position: sticky; top: 50px; height: 25px; }" |
| 380 "#postPadding { height: 200px }</style>" |
| 381 "<div id='scroller'><div id='prePadding'></div><div id='stickyParent'>" |
| 382 "<div id='stickyChild'></div></div><div id='postPadding'></div></div>"); |
| 383 |
| 384 // Make sure that the constraints are cached before scrolling, or they will |
| 385 // simply be calculated post-scroll and thus not require offset correction. |
| 386 |
| 387 LayoutBoxModelObject* stickyParent = |
| 388 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyParent")); |
| 389 stickyParent->updateStickyPositionConstraints(); |
| 390 |
| 391 LayoutBoxModelObject* stickyChild = |
| 392 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyChild")); |
| 393 stickyChild->updateStickyPositionConstraints(); |
| 394 |
| 395 // Scroll the page down. |
| 396 LayoutBoxModelObject* scroller = |
| 397 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 398 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 399 scrollableArea->scrollToAbsolutePosition( |
| 400 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 401 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 402 |
| 403 // The parent is attempting to place itself at the top of the scrollable area, |
| 404 // whilst the child is attempting to be 50 pixels from the top. However, there |
| 405 // is only 25 pixels of space for the child to move into, so it should be |
| 406 // capped by that offset. As always, the child offset is relative. |
| 407 EXPECT_EQ(LayoutSize(0, 50), stickyParent->stickyPositionOffset()); |
| 408 EXPECT_EQ(LayoutSize(0, 25), stickyChild->stickyPositionOffset()); |
| 409 } |
| 410 |
| 411 // Verifies that the sticky constraints are correct in the case of triple |
| 412 // nesting. Triple (or more) nesting must be tested as the grandchild sticky |
| 413 // offset must be corrected for both the containing block and the viewport. |
| 414 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedTripleNestedDiv) { |
| 415 setBodyInnerHTML( |
| 416 "<style>#scroller { height: 200px; width: 100px; overflow-y: auto; }" |
| 417 "#prePadding { height: 50px; }" |
| 418 "#sticky1 { position: sticky; top: 0; height: 100px; }" |
| 419 "#sticky2 { position: sticky; top: 0; height: 75px; }" |
| 420 "#sticky3 { position: sticky; top: 25px; height: 25px; }" |
| 421 "#postPadding { height: 400px }</style>" |
| 422 "<div id='scroller'><div id='prePadding'></div><div id='sticky1'>" |
| 423 "<div id='sticky2'><div id='sticky3'></div></div></div>" |
| 424 "<div id='postPadding'></div></div>"); |
| 425 |
| 426 // Make sure that the constraints are cached before scrolling, or they will |
| 427 // simply be calculated post-scroll and thus not require offset correction. |
| 428 |
| 429 LayoutBoxModelObject* stickyOne = |
| 430 toLayoutBoxModelObject(getLayoutObjectByElementId("sticky1")); |
| 431 stickyOne->updateStickyPositionConstraints(); |
| 432 |
| 433 LayoutBoxModelObject* stickyTwo = |
| 434 toLayoutBoxModelObject(getLayoutObjectByElementId("sticky2")); |
| 435 stickyTwo->updateStickyPositionConstraints(); |
| 436 |
| 437 LayoutBoxModelObject* stickyThree = |
| 438 toLayoutBoxModelObject(getLayoutObjectByElementId("sticky3")); |
| 439 stickyThree->updateStickyPositionConstraints(); |
| 440 |
| 441 // Scroll the page down. |
| 442 LayoutBoxModelObject* scroller = |
| 443 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 444 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 445 scrollableArea->scrollToAbsolutePosition( |
| 446 FloatPoint(scrollableArea->scrollPosition().x(), 100)); |
| 447 ASSERT_EQ(100.0, scrollableArea->scrollPosition().y()); |
| 448 |
| 449 // The grandparent and parent divs are attempting to place themselves at the |
| 450 // top of the scrollable area. The child div is attempting to place itself at |
| 451 // an offset of 25 pixels to the top of the scrollable area. The result of |
| 452 // this sticky offset calculation is quite simple, but internally the child |
| 453 // offset has to offset both against its containing block and against its |
| 454 // viewport ancestor (the grandparent). |
| 455 EXPECT_EQ(LayoutSize(0, 50), stickyOne->stickyPositionOffset()); |
| 456 EXPECT_EQ(LayoutSize(0, 0), stickyTwo->stickyPositionOffset()); |
| 457 EXPECT_EQ(LayoutSize(0, 25), stickyThree->stickyPositionOffset()); |
| 458 } |
| 459 |
| 460 // Verifies that the sticky constraints are correct in the case of tables. |
| 461 // Tables are special as the containing block for all child elements is always |
| 462 // the root level <table>. |
| 463 TEST_F(LayoutBoxModelObjectTest, StickyPositionNestedTableExample) { |
| 464 setBodyInnerHTML( |
| 465 "<style>#scroller { height: 100px; width: 100px; overflow-y: auto; }" |
| 466 "#prePadding { height: 50px; }" |
| 467 "#stickyDiv { position: sticky; top: 0; height: 200px; }" |
| 468 "#table { border-collapse: collapse; }" |
| 469 "#stickyThead { position: sticky; top: 0px; }" |
| 470 "#stickyTr { position: sticky; top: 0px; }" |
| 471 "#stickyTh { position: sticky; top: 0px; }" |
| 472 "#postPadding { height: 200px; }</style>" |
| 473 "<div id='scroller'><div id='prePadding'></div><div id='stickyDiv'>" |
| 474 "<table id='table'><thead id='stickyThead'><tr id='stickyTr'>" |
| 475 "<th id='stickyTh'>Header</th></tr></thread><tbody><tr><td>0</td></tr>" |
| 476 "<tr><td>1</td></tr><tr><td>2</td></tr><tr><td>3</td></tr></tbody>" |
| 477 "</table></div><div id='postPadding'></div></div>"); |
| 478 |
| 479 LOG(INFO) << document().body()->innerHTML(); |
| 480 |
| 481 // Make sure that the constraints are cached before scrolling, or they will |
| 482 // simply be calculated post-scroll and thus not require offset correction. |
| 483 |
| 484 LayoutBoxModelObject* stickyDiv = |
| 485 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyDiv")); |
| 486 stickyDiv->updateStickyPositionConstraints(); |
| 487 |
| 488 LayoutBoxModelObject* stickyThead = |
| 489 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyThead")); |
| 490 stickyThead->updateStickyPositionConstraints(); |
| 491 |
| 492 LayoutBoxModelObject* stickyTr = |
| 493 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTr")); |
| 494 stickyTr->updateStickyPositionConstraints(); |
| 495 |
| 496 LayoutBoxModelObject* stickyTh = |
| 497 toLayoutBoxModelObject(getLayoutObjectByElementId("stickyTh")); |
| 498 stickyTh->updateStickyPositionConstraints(); |
| 499 |
| 500 // Scroll the page down. |
| 501 LayoutBoxModelObject* scroller = |
| 502 toLayoutBoxModelObject(getLayoutObjectByElementId("scroller")); |
| 503 PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea(); |
| 504 scrollableArea->scrollToAbsolutePosition( |
| 505 FloatPoint(scrollableArea->scrollPosition().x(), 150)); |
| 506 ASSERT_EQ(150.0, scrollableArea->scrollPosition().y()); |
| 507 |
| 508 // All sticky elements are attempting to stick to the top of the scrollable |
| 509 // area. For the root sticky div, this requires an offset. All the other |
| 510 // descendant sticky elements are positioned relatively so don't need offset. |
| 511 EXPECT_EQ(LayoutSize(0, 100), stickyDiv->stickyPositionOffset()); |
| 512 EXPECT_EQ(LayoutSize(0, 0), stickyThead->stickyPositionOffset()); |
| 513 EXPECT_EQ(LayoutSize(0, 0), stickyTr->stickyPositionOffset()); |
| 514 EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset()); |
| 515 |
| 516 // If we now scroll to the point where the overall sticky div starts to move, |
| 517 // the table headers should continue to stick to the top of the scrollable |
| 518 // area until they run out of <table> space to move in. |
| 519 |
| 520 scrollableArea->scrollToAbsolutePosition( |
| 521 FloatPoint(scrollableArea->scrollPosition().x(), 275)); |
| 522 ASSERT_EQ(275.0, scrollableArea->scrollPosition().y()); |
| 523 |
| 524 // NOTE(smcgruer): Currently fails - the <th> also moves 12 |
| 525 // NOTE(smcgruer): I'm not sure that 12 is correct. My hankerchief math gets |
| 526 // me something like (275 - 50 - 200) = 25? But it renders correct aisde from |
| 527 // the <th>... |
| 528 EXPECT_EQ(LayoutSize(0, 200), stickyDiv->stickyPositionOffset()); |
| 529 EXPECT_EQ(LayoutSize(0, 12), stickyThead->stickyPositionOffset()); |
| 530 EXPECT_EQ(LayoutSize(0, 0), stickyTr->stickyPositionOffset()); |
| 531 EXPECT_EQ(LayoutSize(0, 0), stickyTh->stickyPositionOffset()); |
| 532 |
| 533 // Finally, if we scroll so that the table is off the top of the page, the |
| 534 // sticky header should go off the top with it. |
| 535 |
| 536 scrollableArea->scrollToAbsolutePosition( |
| 537 FloatPoint(scrollableArea->scrollPosition().x(), 350)); |
| 538 ASSERT_EQ(350.0, scrollableArea->scrollPosition().y()); |
| 539 |
| 540 // TODO(smcgruer): Do EXPECT_EQ for this scenario. It's currently broken |
| 541 // anyway. |
| 542 } |
| 543 |
| 252 } // namespace blink | 544 } // namespace blink |
| OLD | NEW |