| 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..dafe949a4fe452cf1c2edfc470989ad3056f5603 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,226 @@ 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());
|
| +
|
| + // TODO(smcgruer): This fails when it should succeed. The underlying reason is
|
| + // that doing the update for sticky in StyleWillChange doesn't work. The
|
| + // Parent() appears not to be set yet.
|
| + 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);
|
| + // TODO(smcgruer): This fails when it should succeed.
|
| + 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);
|
| + // TODO(smcgruer): This fails when it should succeed.
|
| + 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);
|
| + // TODO(smcgruer): This fails when it should succeed.
|
| + document.getElementById("stickyChild")->OffsetTop();
|
| + // EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean,
|
| + // document.Lifecycle().GetState());
|
| +
|
| + DirtyLayoutAndAssertLifecycle(document);
|
| + // TODO(smcgruer): This fails when it should succeed.
|
| + 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);
|
| +}
|
| +
|
| } // namespace blink
|
|
|