| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/dom/IntersectionObservation.h" | 5 #include "core/dom/IntersectionObservation.h" |
| 6 | 6 |
| 7 #include "core/dom/ElementRareData.h" | 7 #include "core/dom/ElementRareData.h" |
| 8 #include "core/dom/IntersectionObserver.h" | 8 #include "core/dom/IntersectionObserver.h" |
| 9 #include "core/frame/FrameView.h" | |
| 10 #include "core/frame/LocalFrame.h" | |
| 11 #include "core/layout/LayoutBox.h" | |
| 12 #include "core/layout/LayoutView.h" | |
| 13 #include "core/paint/PaintLayer.h" | |
| 14 | 9 |
| 15 namespace blink { | 10 namespace blink { |
| 16 | 11 |
| 17 IntersectionObservation::IntersectionObservation(IntersectionObserver& observer, | 12 IntersectionObservation::IntersectionObservation( |
| 18 Element& target, | 13 IntersectionObserver& observer, |
| 19 bool shouldReportRootBounds) | 14 Element& target, |
| 15 IntersectionGeometry::ReportRootBounds shouldReportRootBounds) |
| 20 : m_observer(observer), | 16 : m_observer(observer), |
| 21 m_target(&target), | 17 m_target(&target), |
| 22 m_shouldReportRootBounds(shouldReportRootBounds), | 18 m_shouldReportRootBounds( |
| 19 shouldReportRootBounds == |
| 20 IntersectionGeometry::ReportRootBounds::kShouldReportRootBounds), |
| 23 m_lastThresholdIndex(0) {} | 21 m_lastThresholdIndex(0) {} |
| 24 | 22 |
| 25 void IntersectionObservation::applyRootMargin(LayoutRect& rect) const { | |
| 26 if (m_shouldReportRootBounds) | |
| 27 m_observer->applyRootMargin(rect); | |
| 28 } | |
| 29 | |
| 30 void IntersectionObservation::initializeGeometry( | |
| 31 IntersectionGeometry& geometry) const { | |
| 32 initializeTargetRect(geometry.targetRect); | |
| 33 geometry.intersectionRect = geometry.targetRect; | |
| 34 initializeRootRect(geometry.rootRect); | |
| 35 geometry.doesIntersect = true; | |
| 36 } | |
| 37 | |
| 38 void IntersectionObservation::initializeTargetRect(LayoutRect& rect) const { | |
| 39 DCHECK(m_target); | |
| 40 LayoutObject* targetLayoutObject = target()->layoutObject(); | |
| 41 DCHECK(targetLayoutObject && targetLayoutObject->isBoxModelObject()); | |
| 42 rect = LayoutRect( | |
| 43 toLayoutBoxModelObject(targetLayoutObject)->borderBoundingBox()); | |
| 44 } | |
| 45 | |
| 46 void IntersectionObservation::initializeRootRect(LayoutRect& rect) const { | |
| 47 LayoutObject* rootLayoutObject = m_observer->rootLayoutObject(); | |
| 48 if (rootLayoutObject->isLayoutView()) | |
| 49 rect = LayoutRect( | |
| 50 toLayoutView(rootLayoutObject)->frameView()->visibleContentRect()); | |
| 51 else if (rootLayoutObject->isBox() && rootLayoutObject->hasOverflowClip()) | |
| 52 rect = LayoutRect(toLayoutBox(rootLayoutObject)->contentBoxRect()); | |
| 53 else | |
| 54 rect = LayoutRect( | |
| 55 toLayoutBoxModelObject(rootLayoutObject)->borderBoundingBox()); | |
| 56 applyRootMargin(rect); | |
| 57 } | |
| 58 | |
| 59 void IntersectionObservation::clipToRoot(IntersectionGeometry& geometry) const { | |
| 60 // Map and clip rect into root element coordinates. | |
| 61 // TODO(szager): the writing mode flipping needs a test. | |
| 62 DCHECK(m_target); | |
| 63 LayoutBox* rootLayoutObject = toLayoutBox(m_observer->rootLayoutObject()); | |
| 64 LayoutObject* targetLayoutObject = target()->layoutObject(); | |
| 65 | |
| 66 geometry.doesIntersect = targetLayoutObject->mapToVisualRectInAncestorSpace( | |
| 67 rootLayoutObject, geometry.intersectionRect, EdgeInclusive); | |
| 68 if (rootLayoutObject->hasOverflowClip()) | |
| 69 geometry.intersectionRect.move(-rootLayoutObject->scrolledContentOffset()); | |
| 70 | |
| 71 if (!geometry.doesIntersect) | |
| 72 return; | |
| 73 LayoutRect rootClipRect(geometry.rootRect); | |
| 74 rootLayoutObject->flipForWritingMode(rootClipRect); | |
| 75 geometry.doesIntersect &= | |
| 76 geometry.intersectionRect.inclusiveIntersect(rootClipRect); | |
| 77 } | |
| 78 | |
| 79 static void mapRectUpToDocument(LayoutRect& rect, | |
| 80 const LayoutObject& layoutObject, | |
| 81 const Document& document) { | |
| 82 FloatQuad mappedQuad = layoutObject.localToAbsoluteQuad( | |
| 83 FloatQuad(FloatRect(rect)), UseTransforms | ApplyContainerFlip); | |
| 84 rect = LayoutRect(mappedQuad.boundingBox()); | |
| 85 } | |
| 86 | |
| 87 static void mapRectDownToDocument(LayoutRect& rect, | |
| 88 LayoutBoxModelObject& layoutObject, | |
| 89 const Document& document) { | |
| 90 FloatQuad mappedQuad = document.layoutView()->ancestorToLocalQuad( | |
| 91 &layoutObject, FloatQuad(FloatRect(rect)), | |
| 92 UseTransforms | ApplyContainerFlip | TraverseDocumentBoundaries); | |
| 93 rect = LayoutRect(mappedQuad.boundingBox()); | |
| 94 } | |
| 95 | |
| 96 void IntersectionObservation::mapTargetRectToTargetFrameCoordinates( | |
| 97 LayoutRect& rect) const { | |
| 98 LayoutObject& targetLayoutObject = *target()->layoutObject(); | |
| 99 Document& targetDocument = target()->document(); | |
| 100 LayoutSize scrollPosition = | |
| 101 LayoutSize(targetDocument.view()->getScrollOffset()); | |
| 102 mapRectUpToDocument(rect, targetLayoutObject, targetDocument); | |
| 103 rect.move(-scrollPosition); | |
| 104 } | |
| 105 | |
| 106 void IntersectionObservation::mapRootRectToRootFrameCoordinates( | |
| 107 LayoutRect& rect) const { | |
| 108 LayoutObject& rootLayoutObject = *m_observer->rootLayoutObject(); | |
| 109 Document& rootDocument = rootLayoutObject.document(); | |
| 110 LayoutSize scrollPosition = | |
| 111 LayoutSize(rootDocument.view()->getScrollOffset()); | |
| 112 mapRectUpToDocument(rect, rootLayoutObject, rootLayoutObject.document()); | |
| 113 rect.move(-scrollPosition); | |
| 114 } | |
| 115 | |
| 116 void IntersectionObservation::mapRootRectToTargetFrameCoordinates( | |
| 117 LayoutRect& rect) const { | |
| 118 LayoutObject& rootLayoutObject = *m_observer->rootLayoutObject(); | |
| 119 Document& targetDocument = target()->document(); | |
| 120 LayoutSize scrollPosition = | |
| 121 LayoutSize(targetDocument.view()->getScrollOffset()); | |
| 122 | |
| 123 if (&targetDocument == &rootLayoutObject.document()) | |
| 124 mapRectUpToDocument(rect, rootLayoutObject, targetDocument); | |
| 125 else | |
| 126 mapRectDownToDocument(rect, toLayoutBoxModelObject(rootLayoutObject), | |
| 127 targetDocument); | |
| 128 | |
| 129 rect.move(-scrollPosition); | |
| 130 } | |
| 131 | |
| 132 static bool isContainingBlockChainDescendant(LayoutObject* descendant, | |
| 133 LayoutObject* ancestor) { | |
| 134 LocalFrame* ancestorFrame = ancestor->document().frame(); | |
| 135 LocalFrame* descendantFrame = descendant->document().frame(); | |
| 136 | |
| 137 if (ancestor->isLayoutView()) | |
| 138 return descendantFrame && descendantFrame->tree().top() == ancestorFrame; | |
| 139 | |
| 140 if (ancestorFrame != descendantFrame) | |
| 141 return false; | |
| 142 | |
| 143 while (descendant && descendant != ancestor) | |
| 144 descendant = descendant->containingBlock(); | |
| 145 return descendant; | |
| 146 } | |
| 147 | |
| 148 bool IntersectionObservation::computeGeometry( | |
| 149 IntersectionGeometry& geometry) const { | |
| 150 // In the first few lines here, before initializeGeometry is called, "return | |
| 151 // true" effectively means "if the previous observed state was that root and | |
| 152 // target were intersecting, then generate a notification indicating that they | |
| 153 // are no longer intersecting." This happens, for example, when root or | |
| 154 // target is removed from the DOM tree and not reinserted before the next | |
| 155 // frame is generated, or display:none is set on the root or target. | |
| 156 Element* targetElement = target(); | |
| 157 if (!targetElement) | |
| 158 return false; | |
| 159 if (!targetElement->isConnected()) | |
| 160 return true; | |
| 161 DCHECK(m_observer); | |
| 162 Element* rootElement = m_observer->root(); | |
| 163 if (rootElement && !rootElement->isConnected()) | |
| 164 return true; | |
| 165 | |
| 166 LayoutObject* rootLayoutObject = m_observer->rootLayoutObject(); | |
| 167 if (!rootLayoutObject || !rootLayoutObject->isBoxModelObject()) | |
| 168 return true; | |
| 169 // TODO(szager): Support SVG | |
| 170 LayoutObject* targetLayoutObject = targetElement->layoutObject(); | |
| 171 if (!targetLayoutObject) | |
| 172 return true; | |
| 173 if (!targetLayoutObject->isBoxModelObject() && !targetLayoutObject->isText()) | |
| 174 return true; | |
| 175 if (!isContainingBlockChainDescendant(targetLayoutObject, rootLayoutObject)) | |
| 176 return true; | |
| 177 | |
| 178 initializeGeometry(geometry); | |
| 179 | |
| 180 clipToRoot(geometry); | |
| 181 | |
| 182 mapTargetRectToTargetFrameCoordinates(geometry.targetRect); | |
| 183 | |
| 184 if (geometry.doesIntersect) | |
| 185 mapRootRectToTargetFrameCoordinates(geometry.intersectionRect); | |
| 186 else | |
| 187 geometry.intersectionRect = LayoutRect(); | |
| 188 | |
| 189 // Small optimization: if we're not going to report root bounds, don't bother | |
| 190 // transforming them to the frame. | |
| 191 if (m_shouldReportRootBounds) | |
| 192 mapRootRectToRootFrameCoordinates(geometry.rootRect); | |
| 193 | |
| 194 return true; | |
| 195 } | |
| 196 | |
| 197 void IntersectionObservation::computeIntersectionObservations( | 23 void IntersectionObservation::computeIntersectionObservations( |
| 198 DOMHighResTimeStamp timestamp) { | 24 DOMHighResTimeStamp timestamp) { |
| 199 IntersectionGeometry geometry; | 25 if (!m_target) |
| 200 if (!computeGeometry(geometry)) | |
| 201 return; | 26 return; |
| 27 Vector<Length> rootMargin(4); |
| 28 rootMargin[0] = m_observer->topMargin(); |
| 29 rootMargin[1] = m_observer->rightMargin(); |
| 30 rootMargin[2] = m_observer->bottomMargin(); |
| 31 rootMargin[3] = m_observer->leftMargin(); |
| 32 IntersectionGeometry geometry( |
| 33 m_observer->rootNode(), target(), rootMargin, |
| 34 m_shouldReportRootBounds |
| 35 ? IntersectionGeometry::ReportRootBounds::kShouldReportRootBounds |
| 36 : IntersectionGeometry::ReportRootBounds::kShouldNotReportRootBounds); |
| 37 geometry.computeGeometry(); |
| 202 | 38 |
| 203 // Some corner cases for threshold index: | 39 // Some corner cases for threshold index: |
| 204 // - If target rect is zero area, because it has zero width and/or zero | 40 // - If target rect is zero area, because it has zero width and/or zero |
| 205 // height, | 41 // height, |
| 206 // only two states are recognized: | 42 // only two states are recognized: |
| 207 // - 0 means not intersecting. | 43 // - 0 means not intersecting. |
| 208 // - 1 means intersecting. | 44 // - 1 means intersecting. |
| 209 // No other threshold crossings are possible. | 45 // No other threshold crossings are possible. |
| 210 // - Otherwise: | 46 // - Otherwise: |
| 211 // - If root and target do not intersect, the threshold index is 0. | 47 // - If root and target do not intersect, the threshold index is 0. |
| 212 // - If root and target intersect but the intersection has zero-area | 48 // - If root and target intersect but the intersection has zero-area |
| 213 // (i.e., they have a coincident edge or corner), we consider the | 49 // (i.e., they have a coincident edge or corner), we consider the |
| 214 // intersection to have "crossed" a zero threshold, but not crossed | 50 // intersection to have "crossed" a zero threshold, but not crossed |
| 215 // any non-zero threshold. | 51 // any non-zero threshold. |
| 216 unsigned newThresholdIndex; | 52 unsigned newThresholdIndex; |
| 217 float newVisibleRatio = 0; | 53 float newVisibleRatio = 0; |
| 218 if (geometry.targetRect.isEmpty()) { | 54 if (geometry.targetRect().isEmpty()) { |
| 219 newThresholdIndex = geometry.doesIntersect ? 1 : 0; | 55 newThresholdIndex = geometry.doesIntersect() ? 1 : 0; |
| 220 } else if (!geometry.doesIntersect) { | 56 } else if (!geometry.doesIntersect()) { |
| 221 newThresholdIndex = 0; | 57 newThresholdIndex = 0; |
| 222 } else { | 58 } else { |
| 223 float intersectionArea = | 59 float intersectionArea = |
| 224 geometry.intersectionRect.size().width().toFloat() * | 60 geometry.intersectionRect().size().width().toFloat() * |
| 225 geometry.intersectionRect.size().height().toFloat(); | 61 geometry.intersectionRect().size().height().toFloat(); |
| 226 float targetArea = geometry.targetRect.size().width().toFloat() * | 62 float targetArea = geometry.targetRect().size().width().toFloat() * |
| 227 geometry.targetRect.size().height().toFloat(); | 63 geometry.targetRect().size().height().toFloat(); |
| 228 newVisibleRatio = intersectionArea / targetArea; | 64 newVisibleRatio = intersectionArea / targetArea; |
| 229 newThresholdIndex = observer().firstThresholdGreaterThan(newVisibleRatio); | 65 newThresholdIndex = observer().firstThresholdGreaterThan(newVisibleRatio); |
| 230 } | 66 } |
| 231 if (m_lastThresholdIndex != newThresholdIndex) { | 67 if (m_lastThresholdIndex != newThresholdIndex) { |
| 232 IntRect snappedRootBounds = pixelSnappedIntRect(geometry.rootRect); | 68 IntRect snappedRootBounds = geometry.rootIntRect(); |
| 233 IntRect* rootBoundsPointer = | 69 IntRect* rootBoundsPointer = |
| 234 m_shouldReportRootBounds ? &snappedRootBounds : nullptr; | 70 m_shouldReportRootBounds ? &snappedRootBounds : nullptr; |
| 235 IntersectionObserverEntry* newEntry = new IntersectionObserverEntry( | 71 IntersectionObserverEntry* newEntry = new IntersectionObserverEntry( |
| 236 timestamp, newVisibleRatio, pixelSnappedIntRect(geometry.targetRect), | 72 timestamp, newVisibleRatio, geometry.targetIntRect(), rootBoundsPointer, |
| 237 rootBoundsPointer, pixelSnappedIntRect(geometry.intersectionRect), | 73 geometry.intersectionIntRect(), target()); |
| 238 target()); | |
| 239 observer().enqueueIntersectionObserverEntry(*newEntry); | 74 observer().enqueueIntersectionObserverEntry(*newEntry); |
| 240 setLastThresholdIndex(newThresholdIndex); | 75 setLastThresholdIndex(newThresholdIndex); |
| 241 } | 76 } |
| 242 } | 77 } |
| 243 | 78 |
| 244 void IntersectionObservation::disconnect() { | 79 void IntersectionObservation::disconnect() { |
| 245 IntersectionObserver* observer = m_observer; | 80 IntersectionObserver* observer = m_observer; |
| 246 clearRootAndRemoveFromTarget(); | 81 clearRootAndRemoveFromTarget(); |
| 247 observer->removeObservation(*this); | 82 observer->removeObservation(*this); |
| 248 } | 83 } |
| 249 | 84 |
| 250 void IntersectionObservation::clearRootAndRemoveFromTarget() { | 85 void IntersectionObservation::clearRootAndRemoveFromTarget() { |
| 251 if (m_target) | 86 if (m_target) |
| 252 target()->ensureIntersectionObserverData().removeObservation(observer()); | 87 target()->ensureIntersectionObserverData().removeObservation(observer()); |
| 253 m_observer.clear(); | 88 m_observer.clear(); |
| 254 } | 89 } |
| 255 | 90 |
| 256 DEFINE_TRACE(IntersectionObservation) { | 91 DEFINE_TRACE(IntersectionObservation) { |
| 257 visitor->trace(m_observer); | 92 visitor->trace(m_observer); |
| 258 visitor->trace(m_target); | 93 visitor->trace(m_target); |
| 259 } | 94 } |
| 260 | 95 |
| 261 } // namespace blink | 96 } // namespace blink |
| OLD | NEW |