From 1275d3aca4b5dab9bdaeac8255fd54fe58649182 Mon Sep 17 00:00:00 2001 From: szager Date: Fri, 25 Mar 2016 14:41:57 -0700 Subject: IntersectionObserver: use edge-inclusive geometry for intersections. Split this out from: https://codereview.chromium.org/1817693002/ BUG=540528 R=ojan@chromium.org,chrishtr@chromium.org Review URL: https://codereview.chromium.org/1826323002 Cr-Commit-Position: refs/heads/master@{#383371} --- .../edge-inclusive-intersection-expected.txt | 40 ++++++++++ .../edge-inclusive-intersection.html | 93 ++++++++++++++++++++++ .../same-document-zero-size-target-expected.txt | 12 +-- .../same-document-zero-size-target.html | 12 +-- .../Source/core/dom/IntersectionObservation.cpp | 77 +++++++++++------- .../Source/core/dom/IntersectionObservation.h | 4 +- .../Source/core/dom/IntersectionObserver.cpp | 7 -- 7 files changed, 196 insertions(+), 49 deletions(-) create mode 100644 third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection-expected.txt create mode 100644 third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection.html diff --git a/third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection-expected.txt b/third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection-expected.txt new file mode 100644 index 0000000..010011a --- /dev/null +++ b/third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection-expected.txt @@ -0,0 +1,40 @@ +Verifies that IntersectionObserver detects edge-inclusive intersections. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS entries.length is 0 +PASS entries.length is 0 +PASS entries.length is 1 +PASS entries[0].boundingClientRect.left is 8 +PASS entries[0].boundingClientRect.right is 108 +PASS entries[0].boundingClientRect.top is 208 +PASS entries[0].boundingClientRect.bottom is 308 +PASS entries[0].intersectionRect.left is 8 +PASS entries[0].intersectionRect.right is 108 +PASS entries[0].intersectionRect.top is 208 +PASS entries[0].intersectionRect.bottom is 208 +PASS entries[0].rootBounds.left is 8 +PASS entries[0].rootBounds.right is 208 +PASS entries[0].rootBounds.top is 8 +PASS entries[0].rootBounds.bottom is 208 +PASS entries[0].target is [object HTMLDivElement] +PASS entries.length is 2 +PASS entries.length is 3 +PASS entries[2].boundingClientRect.left is 8 +PASS entries[2].boundingClientRect.right is 308 +PASS entries[2].boundingClientRect.top is 193 +PASS entries[2].boundingClientRect.bottom is 193 +PASS entries[2].intersectionRect.left is 8 +PASS entries[2].intersectionRect.right is 208 +PASS entries[2].intersectionRect.top is 193 +PASS entries[2].intersectionRect.bottom is 193 +PASS entries[2].rootBounds.left is 8 +PASS entries[2].rootBounds.right is 208 +PASS entries[2].rootBounds.top is 8 +PASS entries[2].rootBounds.bottom is 208 +PASS entries[2].target is [object HTMLDivElement] +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection.html b/third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection.html new file mode 100644 index 0000000..57c3608 --- /dev/null +++ b/third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection.html @@ -0,0 +1,93 @@ + + + + +
+
+
+ + diff --git a/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target-expected.txt b/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target-expected.txt index 6f7880a..4a1e57b 100644 --- a/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target-expected.txt +++ b/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target-expected.txt @@ -6,13 +6,13 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE PASS entries.length is 0 PASS entries.length is 1 PASS entries[0].boundingClientRect.left is 8 -PASS entries[0].boundingClientRect.right is 9 +PASS entries[0].boundingClientRect.right is 8 PASS entries[0].boundingClientRect.top is 408 -PASS entries[0].boundingClientRect.bottom is 409 +PASS entries[0].boundingClientRect.bottom is 408 PASS entries[0].intersectionRect.left is 8 -PASS entries[0].intersectionRect.right is 9 +PASS entries[0].intersectionRect.right is 8 PASS entries[0].intersectionRect.top is 408 -PASS entries[0].intersectionRect.bottom is 409 +PASS entries[0].intersectionRect.bottom is 408 PASS entries[0].rootBounds.left is 0 PASS entries[0].rootBounds.right is 785 PASS entries[0].rootBounds.top is 0 @@ -23,9 +23,9 @@ PASS entries[0].intersectionRect is [object ClientRect] PASS entries[0].rootBounds is [object ClientRect] PASS entries.length is 2 PASS entries[1].boundingClientRect.left is 8 -PASS entries[1].boundingClientRect.right is 9 +PASS entries[1].boundingClientRect.right is 8 PASS entries[1].boundingClientRect.top is 608 -PASS entries[1].boundingClientRect.bottom is 609 +PASS entries[1].boundingClientRect.bottom is 608 PASS entries[1].intersectionRect.left is 0 PASS entries[1].intersectionRect.right is 0 PASS entries[1].intersectionRect.top is 0 diff --git a/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target.html b/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target.html index 4a5b435..215f005 100644 --- a/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target.html +++ b/third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target.html @@ -26,13 +26,13 @@ setTimeout(function() { shouldBeEqualToNumber("entries.length", 1); shouldBeEqualToNumber("entries[0].boundingClientRect.left", 8); - shouldBeEqualToNumber("entries[0].boundingClientRect.right", 9); + shouldBeEqualToNumber("entries[0].boundingClientRect.right", 8); shouldBeEqualToNumber("entries[0].boundingClientRect.top", 408); - shouldBeEqualToNumber("entries[0].boundingClientRect.bottom", 409); + shouldBeEqualToNumber("entries[0].boundingClientRect.bottom", 408); shouldBeEqualToNumber("entries[0].intersectionRect.left", 8); - shouldBeEqualToNumber("entries[0].intersectionRect.right", 9); + shouldBeEqualToNumber("entries[0].intersectionRect.right", 8); shouldBeEqualToNumber("entries[0].intersectionRect.top", 408); - shouldBeEqualToNumber("entries[0].intersectionRect.bottom", 409); + shouldBeEqualToNumber("entries[0].intersectionRect.bottom", 408); shouldBeEqualToNumber("entries[0].rootBounds.left", 0); shouldBeEqualToNumber("entries[0].rootBounds.right", 785); shouldBeEqualToNumber("entries[0].rootBounds.top", 0); @@ -53,9 +53,9 @@ setTimeout(function() { shouldBeEqualToNumber("entries.length", 2); shouldBeEqualToNumber("entries[1].boundingClientRect.left", 8); - shouldBeEqualToNumber("entries[1].boundingClientRect.right", 9); + shouldBeEqualToNumber("entries[1].boundingClientRect.right", 8); shouldBeEqualToNumber("entries[1].boundingClientRect.top", 608); - shouldBeEqualToNumber("entries[1].boundingClientRect.bottom", 609); + shouldBeEqualToNumber("entries[1].boundingClientRect.bottom", 608); shouldBeEqualToNumber("entries[1].intersectionRect.left", 0); shouldBeEqualToNumber("entries[1].intersectionRect.right", 0); shouldBeEqualToNumber("entries[1].intersectionRect.top", 0); diff --git a/third_party/WebKit/Source/core/dom/IntersectionObservation.cpp b/third_party/WebKit/Source/core/dom/IntersectionObservation.cpp index 8a35d27..7b126fbe 100644 --- a/third_party/WebKit/Source/core/dom/IntersectionObservation.cpp +++ b/third_party/WebKit/Source/core/dom/IntersectionObservation.cpp @@ -34,19 +34,20 @@ void IntersectionObservation::applyRootMargin(LayoutRect& rect) const m_observer->applyRootMargin(rect); } +void IntersectionObservation::initializeGeometry(IntersectionGeometry& geometry) const +{ + initializeTargetRect(geometry.targetRect); + geometry.intersectionRect = geometry.targetRect; + initializeRootRect(geometry.rootRect); + geometry.doesIntersect = true; +} + void IntersectionObservation::initializeTargetRect(LayoutRect& rect) const { ASSERT(m_target); LayoutObject* targetLayoutObject = target()->layoutObject(); ASSERT(targetLayoutObject && targetLayoutObject->isBoxModelObject()); rect = toLayoutBoxModelObject(targetLayoutObject)->visualOverflowRect(); - - // TODO(szager): Properly support intersection observations for zero-area targets - // by using edge-inclusive geometry. - if (!rect.size().width()) - rect.setWidth(LayoutUnit(1)); - if (!rect.size().height()) - rect.setHeight(LayoutUnit(1)); } void IntersectionObservation::initializeRootRect(LayoutRect& rect) const @@ -62,7 +63,7 @@ void IntersectionObservation::initializeRootRect(LayoutRect& rect) const applyRootMargin(rect); } -void IntersectionObservation::clipToRoot(LayoutRect& rect, const LayoutRect& rootRect) const +void IntersectionObservation::clipToRoot(IntersectionGeometry& geometry) const { // Map and clip rect into root element coordinates. // TODO(szager): the writing mode flipping needs a test. @@ -70,10 +71,12 @@ void IntersectionObservation::clipToRoot(LayoutRect& rect, const LayoutRect& roo LayoutObject* rootLayoutObject = m_observer->rootLayoutObject(); LayoutObject* targetLayoutObject = target()->layoutObject(); - targetLayoutObject->mapToVisibleRectInAncestorSpace(toLayoutBoxModelObject(rootLayoutObject), rect, nullptr); - LayoutRect rootClipRect(rootRect); + geometry.doesIntersect = targetLayoutObject->mapToVisibleRectInAncestorSpace(toLayoutBoxModelObject(rootLayoutObject), geometry.intersectionRect, nullptr, EdgeInclusive); + if (!geometry.doesIntersect) + return; + LayoutRect rootClipRect(geometry.rootRect); toLayoutBox(rootLayoutObject)->flipForWritingMode(rootClipRect); - rect.intersect(rootClipRect); + geometry.doesIntersect &= geometry.intersectionRect.inclusiveIntersect(rootClipRect); } static void mapRectUpToDocument(LayoutRect& rect, const LayoutObject& layoutObject, const Document& document) @@ -164,22 +167,22 @@ bool IntersectionObservation::computeGeometry(IntersectionGeometry& geometry) co if (!isContainingBlockChainDescendant(targetLayoutObject, rootLayoutObject)) return false; - initializeTargetRect(geometry.targetRect); - geometry.intersectionRect = geometry.targetRect; - initializeRootRect(geometry.rootRect); + initializeGeometry(geometry); - clipToRoot(geometry.intersectionRect, geometry.rootRect); + clipToRoot(geometry); - // TODO(szager): there are some simple optimizations that can be done here: - // - Don't transform rootRect if it's not going to be reported - // - Don't transform intersectionRect if it's empty mapTargetRectToTargetFrameCoordinates(geometry.targetRect); - mapRootRectToTargetFrameCoordinates(geometry.intersectionRect); - mapRootRectToRootFrameCoordinates(geometry.rootRect); - if (geometry.intersectionRect.size().isZero()) + if (geometry.doesIntersect) + mapRootRectToTargetFrameCoordinates(geometry.intersectionRect); + else geometry.intersectionRect = LayoutRect(); + // Small optimization: if we're not going to report root bounds, don't bother + // transforming them to the frame. + if (m_shouldReportRootBounds) + mapRootRectToRootFrameCoordinates(geometry.rootRect); + return true; } @@ -189,15 +192,31 @@ void IntersectionObservation::computeIntersectionObservations(DOMHighResTimeStam if (!computeGeometry(geometry)) return; - float intersectionArea = geometry.intersectionRect.size().width().toFloat() * geometry.intersectionRect.size().height().toFloat(); - float targetArea = geometry.targetRect.size().width().toFloat() * geometry.targetRect.size().height().toFloat(); - if (!targetArea) - return; - float newVisibleRatio = intersectionArea / targetArea; - unsigned newThresholdIndex = observer().firstThresholdGreaterThan(newVisibleRatio); - IntRect snappedRootBounds = pixelSnappedIntRect(geometry.rootRect); - IntRect* rootBoundsPointer = m_shouldReportRootBounds ? &snappedRootBounds : nullptr; + // Some corner cases for threshold index: + // - If target rect is zero area, because it has zero width and/or zero height, + // only two states are recognized: + // - 0 means not intersecting. + // - 1 means intersecting. + // No other threshold crossings are possible. + // - Otherwise: + // - If root and target do not intersect, the threshold index is 0. + // - If root and target intersect but the intersection has zero-area (i.e., they + // have a coincident edge or corner), we consider the intersection to have + // "crossed" a zero threshold, but not crossed any non-zero threshold. + unsigned newThresholdIndex; + if (geometry.targetRect.isEmpty()) { + newThresholdIndex = geometry.doesIntersect ? 1 : 0; + } else if (!geometry.doesIntersect) { + newThresholdIndex = 0; + } else { + float intersectionArea = geometry.intersectionRect.size().width().toFloat() * geometry.intersectionRect.size().height().toFloat(); + float targetArea = geometry.targetRect.size().width().toFloat() * geometry.targetRect.size().height().toFloat(); + float newVisibleRatio = intersectionArea / targetArea; + newThresholdIndex = observer().firstThresholdGreaterThan(newVisibleRatio); + } if (m_lastThresholdIndex != newThresholdIndex) { + IntRect snappedRootBounds = pixelSnappedIntRect(geometry.rootRect); + IntRect* rootBoundsPointer = m_shouldReportRootBounds ? &snappedRootBounds : nullptr; IntersectionObserverEntry* newEntry = new IntersectionObserverEntry( timestamp, pixelSnappedIntRect(geometry.targetRect), diff --git a/third_party/WebKit/Source/core/dom/IntersectionObservation.h b/third_party/WebKit/Source/core/dom/IntersectionObservation.h index 394146d..0d50498 100644 --- a/third_party/WebKit/Source/core/dom/IntersectionObservation.h +++ b/third_party/WebKit/Source/core/dom/IntersectionObservation.h @@ -24,6 +24,7 @@ public: LayoutRect targetRect; LayoutRect intersectionRect; LayoutRect rootRect; + bool doesIntersect; }; IntersectionObserver& observer() const { return *m_observer; } @@ -39,9 +40,10 @@ public: private: void applyRootMargin(LayoutRect&) const; + void initializeGeometry(IntersectionGeometry&) const; void initializeTargetRect(LayoutRect&) const; void initializeRootRect(LayoutRect&) const; - void clipToRoot(LayoutRect&, const LayoutRect&) const; + void clipToRoot(IntersectionGeometry&) const; void mapTargetRectToTargetFrameCoordinates(LayoutRect&) const; void mapRootRectToRootFrameCoordinates(LayoutRect&) const; void mapRootRectToTargetFrameCoordinates(LayoutRect&) const; diff --git a/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp b/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp index 847b288..dac0764 100644 --- a/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp +++ b/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp @@ -290,13 +290,6 @@ void IntersectionObserver::applyRootMargin(LayoutRect& rect) const unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const { unsigned result = 0; - - // Special handling for zero threshold, which means "any non-zero number of pixels." - // If the ratio is zero, then it should be treated as smaller than any threshold, - // even a zero threshold. - if (!ratio) - return 0; - while (result < m_thresholds.size() && m_thresholds[result] <= ratio) ++result; return result; -- cgit v1.1