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