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

Unified Diff: third_party/WebKit/Source/core/dom/ElementTest.cpp

Issue 2825343003: Clean compositing inputs for location APIs for sticky-affected elements. (Closed)
Patch Set: Created 3 years, 8 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 side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698