diff options
7 files changed, 196 insertions, 49 deletions
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 @@ +<!DOCTYPE html> +<script src="../resources/js-test.js"></script> +<script src="helper-functions.js"></script> +<style> +#root { + width: 200px; + height: 200px; + overflow: visible; +} +#target { + background-color: green; +} +</style> +<div id="root"> + <div id="target" style="width: 100px; height: 100px; transform: translateY(250px)"></div> +</div> + +<script> +description("Verifies that IntersectionObserver detects edge-inclusive intersections."); +var root = document.getElementById('root'); +var target = document.getElementById('target'); +var entries = []; +var observer = new IntersectionObserver( + changes => { entries.push(...changes) }, + { root: root } +); + +onload = function() { + observer.observe(target); + entries.push(...observer.takeRecords()); + shouldBeEqualToNumber("entries.length", 0); + requestAnimationFrame(() => { requestAnimationFrame(step0) }); +}; + +function step0() { + entries.push(...observer.takeRecords()); + shouldBeEqualToNumber("entries.length", 0); + target.style.transform = "translateY(200px)"; + requestAnimationFrame(step1); +} + +function step1() { + entries.push(...observer.takeRecords()); + shouldBeEqualToNumber("entries.length", 1); + if (entries.length > 0) { + shouldBeEqualToNumber("entries[0].boundingClientRect.left", 8); + shouldBeEqualToNumber("entries[0].boundingClientRect.right", 108); + shouldBeEqualToNumber("entries[0].boundingClientRect.top", 208); + shouldBeEqualToNumber("entries[0].boundingClientRect.bottom", 308); + shouldBeEqualToNumber("entries[0].intersectionRect.left", 8); + shouldBeEqualToNumber("entries[0].intersectionRect.right", 108); + shouldBeEqualToNumber("entries[0].intersectionRect.top", 208); + shouldBeEqualToNumber("entries[0].intersectionRect.bottom", 208); + shouldBeEqualToNumber("entries[0].rootBounds.left", 8); + shouldBeEqualToNumber("entries[0].rootBounds.right", 208); + shouldBeEqualToNumber("entries[0].rootBounds.top", 8); + shouldBeEqualToNumber("entries[0].rootBounds.bottom", 208); + shouldEvaluateToSameObject("entries[0].target", target); + } + target.style.transform = "translateY(201px)"; + requestAnimationFrame(step2); +} + +function step2() { + entries.push(...observer.takeRecords()); + shouldBeEqualToNumber("entries.length", 2); + target.style.height = "0px"; + target.style.width = "300px"; + target.style.transform = "translateY(185px)"; + requestAnimationFrame(step3); +} + +function step3() { + entries.push(...observer.takeRecords()); + shouldBeEqualToNumber("entries.length", 3); + if (entries.length > 2) { + shouldBeEqualToNumber("entries[2].boundingClientRect.left", 8); + shouldBeEqualToNumber("entries[2].boundingClientRect.right", 308); + shouldBeEqualToNumber("entries[2].boundingClientRect.top", 193); + shouldBeEqualToNumber("entries[2].boundingClientRect.bottom", 193); + shouldBeEqualToNumber("entries[2].intersectionRect.left", 8); + shouldBeEqualToNumber("entries[2].intersectionRect.right", 208); + shouldBeEqualToNumber("entries[2].intersectionRect.top", 193); + shouldBeEqualToNumber("entries[2].intersectionRect.bottom", 193); + shouldBeEqualToNumber("entries[2].rootBounds.left", 8); + shouldBeEqualToNumber("entries[2].rootBounds.right", 208); + shouldBeEqualToNumber("entries[2].rootBounds.top", 8); + shouldBeEqualToNumber("entries[2].rootBounds.bottom", 208); + shouldEvaluateToSameObject("entries[2].target", target); + } + finishJSTest(); +} +</script> 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; |