Chromium Code Reviews| Index: third_party/WebKit/Source/core/dom/ElementTest.cpp |
| diff --git a/third_party/WebKit/Source/core/dom/ElementTest.cpp b/third_party/WebKit/Source/core/dom/ElementTest.cpp |
| index 079936b6cbbc31ea3a4823a28bcda2c3799e11ca..df2cb169a37955468ec26b5e2569ec25d5191d0b 100644 |
| --- a/third_party/WebKit/Source/core/dom/ElementTest.cpp |
| +++ b/third_party/WebKit/Source/core/dom/ElementTest.cpp |
| @@ -4,15 +4,26 @@ |
| #include "core/dom/Element.h" |
| +#include <memory> |
| #include "core/dom/Document.h" |
| #include "core/frame/FrameView.h" |
| #include "core/html/HTMLHtmlElement.h" |
| +#include "core/layout/LayoutBoxModelObject.h" |
| +#include "core/paint/PaintLayer.h" |
| #include "core/testing/DummyPageHolder.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| -#include <memory> |
| namespace blink { |
| +namespace { |
| +void DirtyLayoutAndAssertLifecycle(Document& document) { |
| + // Dirty layout to reset the lifecycle. |
| + document.body()->setAttribute("style", "background: red;"); |
| + ASSERT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| + document.Lifecycle().GetState()); |
| +} |
| +} // namespace |
| + |
| TEST(ElementTest, SupportsFocus) { |
| std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(); |
| Document& document = page_holder->GetDocument(); |
| @@ -23,4 +34,219 @@ TEST(ElementTest, SupportsFocus) { |
| << "<html> with designMode=on should be focusable."; |
| } |
| +// Verifies that calling getBoundingClientRect cleans compositor inputs. |
| +// Cleaning the compositor inputs is required so that position:sticky elements |
| +// and their descendants have the correct location. |
| +TEST(ElementTest, GetBoundingClientRectUpdatesCompositorInputs) { |
| + std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(); |
| + Document& document = page_holder->GetDocument(); |
| + |
| + document.View()->UpdateAllLifecyclePhases(); |
| + EXPECT_EQ(DocumentLifecycle::kPaintClean, document.Lifecycle().GetState()); |
| + |
| + document.body()->setInnerHTML( |
| + "<div id='ancestor'>" |
| + " <div id='sticky' style='position:sticky;'>" |
| + " <div id='stickyChild'></div>" |
| + " </div>" |
| + " <div id='nonSticky'></div>" |
| + "</div>"); |
| + EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| + document.Lifecycle().GetState()); |
| + |
| + // Asking for any element that is not affected by a sticky element should only |
| + // advance us to layout clean. |
| + document.getElementById("ancestor")->getBoundingClientRect(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + document.getElementById("nonSticky")->getBoundingClientRect(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + // However, asking for either the sticky element or it's descendents should |
| + // clean compositing inputs as well. |
| + document.getElementById("sticky")->getBoundingClientRect(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + |
| + DirtyLayoutAndAssertLifecycle(document); |
| + document.getElementById("stickyChild")->getBoundingClientRect(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| +} |
| + |
| +// Verifies that calling scrollIntoView* cleans compositor inputs. Cleaning the |
| +// compositor inputs is required so that position:sticky elements and their |
| +// descendants have the correct location. |
| +TEST(ElementTest, ScrollIntoViewUpdatesCompositorInputs) { |
| + std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(); |
| + Document& document = page_holder->GetDocument(); |
| + |
| + document.View()->UpdateAllLifecyclePhases(); |
| + EXPECT_EQ(DocumentLifecycle::kPaintClean, document.Lifecycle().GetState()); |
| + |
| + document.body()->setInnerHTML( |
| + "<div id='ancestor'>" |
| + " <div id='sticky' style='position:sticky;'>" |
| + " <div id='stickyChild'></div>" |
| + " </div>" |
| + " <div id='nonSticky'></div>" |
| + "</div>"); |
| + EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| + document.Lifecycle().GetState()); |
| + |
| + // Scrolling any element that is not affected by a sticky element should only |
| + // advance us to layout clean. |
| + document.getElementById("ancestor")->scrollIntoView(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + document.getElementById("nonSticky")->scrollIntoView(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + document.getElementById("ancestor")->scrollIntoViewIfNeeded(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + document.getElementById("nonSticky")->scrollIntoViewIfNeeded(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + // However, scrolling based on either the sticky element or it's descendents |
| + // should clean compositing inputs as well. |
| + document.getElementById("sticky")->scrollIntoView(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + |
| + DirtyLayoutAndAssertLifecycle(document); |
| + document.getElementById("stickyChild")->scrollIntoView(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + |
| + DirtyLayoutAndAssertLifecycle(document); |
| + document.getElementById("sticky")->scrollIntoViewIfNeeded(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + |
| + DirtyLayoutAndAssertLifecycle(document); |
| + document.getElementById("stickyChild")->scrollIntoViewIfNeeded(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| +} |
| + |
| +// Verifies that calling offsetTop/offsetLeft cleans compositor inputs. |
| +// Cleaning the compositor inputs is required so that position:sticky elements |
| +// and their descendants have the correct location. |
| +TEST(ElementTest, OffsetTopAndLeftUpdateCompositorInputs) { |
| + std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(); |
| + Document& document = page_holder->GetDocument(); |
| + |
| + document.View()->UpdateAllLifecyclePhases(); |
| + EXPECT_EQ(DocumentLifecycle::kPaintClean, document.Lifecycle().GetState()); |
| + |
| + document.body()->setInnerHTML( |
| + "<div id='ancestor'>" |
| + " <div id='sticky' style='position:sticky;'>" |
| + " <div id='stickyChild'></div>" |
| + " </div>" |
| + " <div id='nonSticky'></div>" |
| + "</div>"); |
| + EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| + document.Lifecycle().GetState()); |
| + |
| + // Asking for any element that is not affected by a sticky element should only |
| + // advance us to layout clean. |
| + document.getElementById("ancestor")->OffsetTop(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + document.getElementById("ancestor")->OffsetLeft(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + document.getElementById("nonSticky")->OffsetTop(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + document.getElementById("nonSticky")->OffsetLeft(); |
| + EXPECT_EQ(DocumentLifecycle::kLayoutClean, document.Lifecycle().GetState()); |
| + |
| + // However, asking for either the sticky element or it's descendents should |
| + // clean compositing inputs as well. |
| + document.getElementById("sticky")->OffsetTop(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + |
| + DirtyLayoutAndAssertLifecycle(document); |
| + document.getElementById("sticky")->OffsetLeft(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + |
| + DirtyLayoutAndAssertLifecycle(document); |
| + document.getElementById("stickyChild")->OffsetTop(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + |
| + DirtyLayoutAndAssertLifecycle(document); |
| + document.getElementById("stickyChild")->OffsetLeft(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| +} |
| + |
| +TEST(ElementTest, OffsetTopAndLeftCorrectForStickyElementsAfterInsertion) { |
| + std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(); |
| + Document& document = page_holder->GetDocument(); |
| + |
| + document.body()->setInnerHTML( |
| + "<style>body { margin: 0 }" |
| + "#scroller { overflow: scroll; height: 100px; width: 100px; }" |
| + "#sticky { height: 25px; position: sticky; top: 0; left: 25px; }" |
| + "#padding { height: 500px; width: 300px; }</style>" |
| + "<div id='scroller'><div id='writer'></div><div id='sticky'></div>" |
| + "<div id='padding'></div></div>"); |
| + document.View()->UpdateAllLifecyclePhases(); |
| + |
| + Element* scroller = document.getElementById("scroller"); |
| + Element* writer = document.getElementById("writer"); |
| + Element* sticky = document.getElementById("sticky"); |
| + |
| + ASSERT_TRUE(scroller); |
| + ASSERT_TRUE(writer); |
| + ASSERT_TRUE(sticky); |
| + |
| + scroller->scrollTo(0, 200.0); |
| + |
| + // After scrolling, the position:sticky element should be offset to stay at |
| + // the top of the viewport. |
| + EXPECT_EQ(scroller->scrollTop(), sticky->OffsetTop()); |
| + |
| + // Insert a new <div> above the sticky. This will dirty layout. |
| + writer->setInnerHTML("<div style='height: 100px;'></div>"); |
| + EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| + document.Lifecycle().GetState()); |
| + |
| + // Querying the new offset of the sticky element should now cause both layout |
| + // and compositing inputs to be clean and should return the correct offset. |
| + int offset_top = sticky->OffsetTop(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + EXPECT_FALSE(sticky->GetLayoutBoxModelObject() |
| + ->Layer() |
| + ->NeedsCompositingInputsUpdate()); |
| + EXPECT_EQ(scroller->scrollTop(), offset_top); |
| + |
| + scroller->scrollTo(50.0, 0); |
| + |
| + // After scrolling, the position:sticky element should be offset to stay 25 |
| + // pixels from the left of the viewport. |
| + EXPECT_EQ(scroller->scrollLeft() + 25, sticky->OffsetLeft()); |
| + |
| + // Insert a new <div> above the sticky. This will dirty layout. |
| + writer->setInnerHTML("<div style='width: 700px;'></div>"); |
| + EXPECT_EQ(DocumentLifecycle::kVisualUpdatePending, |
| + document.Lifecycle().GetState()); |
| + |
| + // Again getting the offset should cause layout and compositing to be clean. |
| + int offset_left = sticky->OffsetLeft(); |
| + EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean, |
| + document.Lifecycle().GetState()); |
| + EXPECT_FALSE(sticky->GetLayoutBoxModelObject() |
| + ->Layer() |
| + ->NeedsCompositingInputsUpdate()); |
| + EXPECT_EQ(scroller->scrollLeft() + 25, offset_left); |
| +} |
| + |
|
flackr
2017/04/27 19:20:51
Can you test isInStickySubtree addition (which of
|
| } // namespace blink |