summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection-expected.txt40
-rw-r--r--third_party/WebKit/LayoutTests/intersection-observer/edge-inclusive-intersection.html93
-rw-r--r--third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target-expected.txt12
-rw-r--r--third_party/WebKit/LayoutTests/intersection-observer/same-document-zero-size-target.html12
-rw-r--r--third_party/WebKit/Source/core/dom/IntersectionObservation.cpp77
-rw-r--r--third_party/WebKit/Source/core/dom/IntersectionObservation.h4
-rw-r--r--third_party/WebKit/Source/core/dom/IntersectionObserver.cpp7
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;