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