| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "core/dom/IntersectionObservation.h" |
| |
| #include "core/dom/ElementRareData.h" |
| #include "core/dom/IntersectionObserver.h" |
| #include "core/frame/FrameView.h" |
| #include "core/layout/LayoutBox.h" |
| #include "core/layout/LayoutText.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/paint/PaintLayer.h" |
| |
| namespace blink { |
| |
| IntersectionObservation::IntersectionObservation(IntersectionObserver& observer, Element& target, bool shouldReportRootBounds) |
| : m_observer(observer) |
| , m_target(target.ensureIntersectionObserverData().createWeakPtr(&target)) |
| , m_active(true) |
| , m_shouldReportRootBounds(shouldReportRootBounds) |
| , m_lastThresholdIndex(0) |
| { |
| } |
| |
| void IntersectionObservation::initializeGeometry(IntersectionGeometry& geometry) |
| { |
| ASSERT(m_target); |
| LayoutObject* targetLayoutObject = m_target->layoutObject(); |
| if (targetLayoutObject->isBoxModelObject()) |
| geometry.targetRect = toLayoutBoxModelObject(targetLayoutObject)->visualOverflowRect(); |
| else |
| geometry.targetRect = toLayoutText(targetLayoutObject)->visualOverflowRect(); |
| geometry.intersectionRect = geometry.targetRect; |
| } |
| |
| void IntersectionObservation::clipToRoot(LayoutRect& rect) |
| { |
| // Map and clip rect into root element coordinates. |
| // TODO(szager): the writing mode flipping needs a test. |
| ASSERT(m_target); |
| LayoutObject* rootLayoutObject = m_observer->rootLayoutObject(); |
| LayoutObject* targetLayoutObject = m_target->layoutObject(); |
| targetLayoutObject->mapToVisibleRectInAncestorSpace(toLayoutBoxModelObject(rootLayoutObject), rect, nullptr); |
| if (rootLayoutObject->hasOverflowClip()) { |
| LayoutBox* rootLayoutBox = toLayoutBox(rootLayoutObject); |
| LayoutRect clipRect(LayoutPoint(), LayoutSize(rootLayoutBox->layer()->size())); |
| rootLayoutBox->flipForWritingMode(rect); |
| rect.intersect(clipRect); |
| rootLayoutBox->flipForWritingMode(rect); |
| } |
| } |
| |
| void IntersectionObservation::clipToFrameView(IntersectionGeometry& geometry) |
| { |
| Element* rootElement = m_observer->root(); |
| LayoutObject* rootLayoutObject = m_observer->rootLayoutObject(); |
| if (rootElement == rootElement->document().documentElement()) { |
| geometry.rootRect = LayoutRect(rootElement->document().view()->visibleContentRect()); |
| geometry.intersectionRect.intersect(geometry.rootRect); |
| } else { |
| if (rootLayoutObject->isBox()) |
| geometry.rootRect = LayoutRect(toLayoutBox(rootLayoutObject)->absoluteContentBox()); |
| else |
| geometry.rootRect = LayoutRect(rootLayoutObject->absoluteBoundingBoxRect()); |
| } |
| |
| LayoutPoint scrollPosition(rootElement->document().view()->scrollPosition()); |
| geometry.targetRect.moveBy(-scrollPosition); |
| geometry.intersectionRect.moveBy(-scrollPosition); |
| geometry.rootRect.moveBy(-scrollPosition); |
| } |
| |
| static void mapRectToDocumentCoordinates(LayoutObject& layoutObject, LayoutRect& rect) |
| { |
| rect = LayoutRect(layoutObject.localToAbsoluteQuad(FloatQuad(FloatRect(rect)), UseTransforms | ApplyContainerFlip | TraverseDocumentBoundaries).boundingBox()); |
| } |
| |
| bool IntersectionObservation::computeGeometry(IntersectionGeometry& geometry) |
| { |
| ASSERT(m_target); |
| LayoutObject* rootLayoutObject = m_observer->rootLayoutObject(); |
| LayoutObject* targetLayoutObject = m_target->layoutObject(); |
| if (!rootLayoutObject->isBoxModelObject()) |
| return false; |
| if (!targetLayoutObject->isBoxModelObject() && !targetLayoutObject->isText()) |
| return false; |
| |
| // Initialize targetRect and intersectionRect to bounds of target, in target's coordinate space. |
| initializeGeometry(geometry); |
| |
| // TODO(szager): Support intersection observations for zero-area targets. For now, we just |
| // punt on the observation. |
| if (!geometry.targetRect.size().width() || !geometry.targetRect.size().height()) |
| return false; |
| |
| // Clip intersectionRect to the root, and map it to root coordinates. |
| clipToRoot(geometry.intersectionRect); |
| |
| // Map targetRect into document coordinates. |
| mapRectToDocumentCoordinates(*targetLayoutObject, geometry.targetRect); |
| |
| // Map intersectionRect into document coordinates. |
| mapRectToDocumentCoordinates(*rootLayoutObject, geometry.intersectionRect); |
| |
| // Clip intersectionRect to FrameView visible area if necessary, and map all geometry to frame coordinates. |
| clipToFrameView(geometry); |
| |
| if (geometry.intersectionRect.size().isZero()) |
| geometry.intersectionRect = LayoutRect(); |
| if (!m_shouldReportRootBounds) |
| geometry.rootRect = LayoutRect(); |
| |
| return true; |
| } |
| |
| void IntersectionObservation::computeIntersectionObservations(double timestamp) |
| { |
| // Pre-oilpan, there will be a delay between the time when the target Element gets deleted |
| // (because its ref count dropped to zero) and when this IntersectionObservation gets |
| // deleted (during the next gc run, because the target Element is the only thing keeping |
| // the IntersectionObservation alive). During that interval, we need to check that m_target |
| // hasn't been cleared. |
| Element* targetElement = target(); |
| if (!targetElement || !isActive()) |
| return; |
| LayoutObject* targetLayoutObject = targetElement->layoutObject(); |
| // TODO(szager): Support SVG |
| if (!targetLayoutObject || (!targetLayoutObject->isBox() && !targetLayoutObject->isInline())) |
| return; |
| |
| IntersectionGeometry geometry; |
| 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); |
| if (m_lastThresholdIndex != newThresholdIndex) { |
| IntersectionObserverEntry* newEntry = new IntersectionObserverEntry( |
| timestamp / 1000.0, |
| pixelSnappedIntRect(geometry.targetRect), |
| pixelSnappedIntRect(geometry.rootRect), |
| pixelSnappedIntRect(geometry.intersectionRect), |
| targetElement); |
| observer().enqueueIntersectionObserverEntry(*newEntry); |
| } |
| setLastThresholdIndex(newThresholdIndex); |
| } |
| |
| void IntersectionObservation::disconnect() |
| { |
| if (m_target) |
| m_target->ensureIntersectionObserverData().removeObservation(this->observer()); |
| m_observer->removeObservation(*this); |
| m_observer.clear(); |
| } |
| |
| DEFINE_TRACE(IntersectionObservation) |
| { |
| visitor->trace(m_observer); |
| visitor->trace(m_target); |
| } |
| |
| } // namespace blink |