bool hitTestFlow(const RenderBlockFlow& flow, const Layout& layout, const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { if (hitTestAction != HitTestForeground) return false; if (!layout.runCount()) return false; RenderStyle& style = flow.style(); if (style.visibility() != VISIBLE || style.pointerEvents() == PE_NONE) return false; RenderObject& renderer = *flow.firstChild(); LayoutRect rangeRect = locationInContainer.boundingBox(); rangeRect.moveBy(-accumulatedOffset); auto resolver = lineResolver(flow, layout); for (FloatRect lineRect : resolver.rangeForRect(rangeRect)) { lineRect.moveBy(accumulatedOffset); if (!locationInContainer.intersects(lineRect)) continue; renderer.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToRectBasedTestResult(renderer.node(), request, locationInContainer, lineRect)) return true; } return false; }
bool SVGInlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, result.hitTestRequest(), layoutObject().style()->pointerEvents()); bool isVisible = layoutObject().style()->visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if (hitRules.canHitBoundingBox || (hitRules.canHitStroke && (layoutObject().style()->svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (layoutObject().style()->svgStyle().hasFill() || !hitRules.requireFill))) { FloatPointWillBeLayoutPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); FloatRectWillBeLayoutRect rect(boxOrigin, size()); // FIXME: both calls to rawValue() below is temporary and should be removed once the transition // to LayoutUnit-based types is complete (crbug.com/321237) if (locationInContainer.intersects(rect.rawValue())) { layoutObject().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToListBasedTestResult(layoutObject().node(), locationInContainer, rect.rawValue())) return true; } } } return false; }
bool SVGInlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, result.hitTestRequest(), lineLayoutItem().style()->pointerEvents()); bool isVisible = lineLayoutItem().style()->visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if (hitRules.canHitBoundingBox || (hitRules.canHitStroke && (lineLayoutItem().style()->svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (lineLayoutItem().style()->svgStyle().hasFill() || !hitRules.requireFill))) { LayoutPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); LayoutRect rect(boxOrigin, size()); if (locationInContainer.intersects(rect)) { LineLayoutSVGInlineText lineLayoutItem = LineLayoutSVGInlineText(this->lineLayoutItem()); ASSERT(lineLayoutItem.scalingFactor()); float baseline = lineLayoutItem.scaledFont().fontMetrics().floatAscent() / lineLayoutItem.scalingFactor(); FloatPoint floatLocation = FloatPoint(locationInContainer.point()); for (const SVGTextFragment& fragment : m_textFragments) { FloatQuad fragmentQuad = fragment.boundingQuad(baseline); if (fragmentQuad.containsPoint(floatLocation)) { lineLayoutItem.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToListBasedTestResult(lineLayoutItem.node(), locationInContainer, rect)) return true; } } } } } return false; }
bool SVGInlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, result.hitTestRequest(), getLineLayoutItem().style()->pointerEvents()); bool isVisible = getLineLayoutItem().style()->visibility() == EVisibility::Visible; if (isVisible || !hitRules.requireVisible) { if (hitRules.canHitBoundingBox || (hitRules.canHitStroke && (getLineLayoutItem().style()->svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (getLineLayoutItem().style()->svgStyle().hasFill() || !hitRules.requireFill))) { LayoutRect rect(topLeft(), LayoutSize(logicalWidth(), logicalHeight())); rect.moveBy(accumulatedOffset); if (locationInContainer.intersects(rect)) { LineLayoutSVGInlineText lineLayoutItem = LineLayoutSVGInlineText(this->getLineLayoutItem()); const SimpleFontData* fontData = lineLayoutItem.scaledFont().primaryFont(); DCHECK(fontData); if (!fontData) return false; DCHECK(lineLayoutItem.scalingFactor()); float baseline = fontData->getFontMetrics().floatAscent() / lineLayoutItem.scalingFactor(); FloatPoint floatLocation = FloatPoint(locationInContainer.point()); for (const SVGTextFragment& fragment : m_textFragments) { FloatQuad fragmentQuad = fragment.boundingQuad(baseline); if (fragmentQuad.containsPoint(floatLocation)) { lineLayoutItem.updateHitTestResult( result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (result.addNodeToListBasedTestResult(lineLayoutItem.node(), locationInContainer, rect) == StopHitTesting) return true; } } } } } return false; }
// Hit Testing bool RenderRegion::hitTestFlowThreadContents(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { if (!isValid() || action != HitTestForeground) return false; LayoutRect boundsRect = borderBoxRectInRegion(locationInContainer.region()); boundsRect.moveBy(accumulatedOffset); if (visibleToHitTesting() && locationInContainer.intersects(boundsRect)) { if (m_flowThread->hitTestFlowThreadPortionInRegion(this, flowThreadPortionRect(), flowThreadPortionOverflowRect(), request, result, locationInContainer, LayoutPoint(accumulatedOffset.x() + borderLeft() + paddingLeft(), accumulatedOffset.y() + borderTop() + paddingTop()))) return true; } return false; }
bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) { if (isLineBreak()) return false; FloatPoint boxOrigin = locationIncludingFlipping(); boxOrigin.moveBy(accumulatedOffset); FloatRect rect(boxOrigin, size()); if (m_truncation != cFullTruncation && visibleToHitTestRequest(request) && locationInContainer.intersects(rect)) { renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset))); if (!result.addNodeToRectBasedTestResult(renderer().node(), request, locationInContainer, rect)) return true; } return false; }
bool InlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) { if (isLineBreak() || m_truncation == cFullTruncation) return false; LayoutPoint boxOrigin = locationIncludingFlipping(); boxOrigin.moveBy(accumulatedOffset); LayoutRect rect(boxOrigin, size()); if (visibleToHitTestRequest(result.hitTestRequest()) && locationInContainer.intersects(rect)) { getLineLayoutItem().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset))); if (result.addNodeToListBasedTestResult(getLineLayoutItem().node(), locationInContainer, rect) == StopHitTesting) return true; } return false; }
bool InlineBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /* lineBottom */) { // Hit test all phases of replaced elements atomically, as though the replaced element established its // own stacking context. (See Appendix E.2, section 6.4 on inline block/table elements in the CSS2.1 // specification.) LayoutPoint childPoint = accumulatedOffset; if (parent()->lineLayoutItem().hasFlippedBlocksWritingMode()) // Faster than calling containingBlock(). childPoint = layoutObject().containingBlock()->flipForWritingModeForChild(&toLayoutBox(layoutObject()), childPoint); if (lineLayoutItem().style()->hasBorderRadius()) { LayoutRect borderRect = logicalFrameRect(); borderRect.moveBy(accumulatedOffset); FloatRoundedRect border = lineLayoutItem().style()->getRoundedBorderFor(borderRect); if (!locationInContainer.intersects(border)) return false; } return lineLayoutItem().hitTest(result, locationInContainer, childPoint); }
bool RenderMultiColumnSet::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { LayoutPoint adjustedLocation = accumulatedOffset + location(); // Check our bounds next. For this purpose always assume that we can only be hit in the // foreground phase (which is true for replaced elements like images). // FIXME: Once we support overflow, we need to intersect with that and not with the bounds rect. LayoutRect boundsRect = borderBoxRectInRegion(locationInContainer.region()); boundsRect.moveBy(adjustedLocation); if (!visibleToHitTesting() || action != HitTestForeground || !locationInContainer.intersects(boundsRect)) return false; // The point is in one specific column. Since columns can't overlap, we don't ever have to test // multiple columns. Put the // FIXME: It would be nice to jump right to the specific column by just doing math on the point. Since columns // can't overlap, we shouldn't have to walk every column like this. The old column code walked all the columns, though, // so this is no worse. We'd have to watch out for rect-based hit testing, though, which actually could overlap // multiple columns. LayoutUnit colGap = columnGap(); unsigned colCount = columnCount(); for (unsigned i = 0; i < colCount; i++) { // First we get the column rect, which is in our local coordinate space, and we make it physical and apply // the hit test offset to it. That gives us the physical location that we want to paint the column at. LayoutRect colRect = columnRectAt(i); flipForWritingMode(colRect); colRect.moveBy(adjustedLocation); // Next we get the portion of the flow thread that corresponds to this column. LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); // Now get the overflow rect that corresponds to the column. LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); // Do the hit test with the computed rects. if (flowThread()->hitTestFlowThreadPortionInRegion(this, flowThreadPortion, flowThreadOverflowPortion, request, result, locationInContainer, colRect.location())) return true; } updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); return !result.addNodeToRectBasedTestResult(node(), request, locationInContainer, boundsRect); }
bool LayoutSVGRoot::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { LayoutPoint pointInParent = locationInContainer.point() - toLayoutSize(accumulatedOffset); LayoutPoint pointInBorderBox = pointInParent - toLayoutSize(location()); // Only test SVG content if the point is in our content box, or in case we // don't clip to the viewport, the visual overflow rect. // FIXME: This should be an intersection when rect-based hit tests are supported by nodeAtFloatPoint. if (contentBoxRect().contains(pointInBorderBox) || (!shouldApplyViewportClip() && visualOverflowRect().contains(pointInBorderBox))) { const AffineTransform& localToParentTransform = this->localToParentTransform(); if (localToParentTransform.isInvertible()) { FloatPoint localPoint = localToParentTransform.inverse().mapPoint(FloatPoint(pointInParent)); for (LayoutObject* child = lastChild(); child; child = child->previousSibling()) { // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet. if (child->nodeAtFloatPoint(result, localPoint, hitTestAction)) { updateHitTestResult(result, pointInBorderBox); if (result.addNodeToListBasedTestResult(child->node(), locationInContainer) == StopHitTesting) return true; } } } } // If we didn't early exit above, we've just hit the container <svg> element. Unlike SVG 1.1, 2nd Edition allows container elements to be hit. if ((hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) && visibleToHitTestRequest(result.hitTestRequest())) { // Only return true here, if the last hit testing phase 'BlockBackground' (or 'ChildBlockBackground' - depending on context) is executed. // If we'd return true in the 'Foreground' phase, hit testing would stop immediately. For SVG only trees this doesn't matter. // Though when we have a <foreignObject> subtree we need to be able to detect hits on the background of a <div> element. // If we'd return true here in the 'Foreground' phase, we are not able to detect these hits anymore. LayoutRect boundsRect(accumulatedOffset + location(), size()); if (locationInContainer.intersects(boundsRect)) { updateHitTestResult(result, pointInBorderBox); if (result.addNodeToListBasedTestResult(node(), locationInContainer, boundsRect) == StopHitTesting) return true; } } return false; }
bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, renderer().style()->pointerEvents()); bool isVisible = renderer().style()->visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if ((hitRules.canHitStroke && (renderer().style()->svgStyle()->hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (renderer().style()->svgStyle()->hasFill() || !hitRules.requireFill))) { FloatPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); FloatRect rect(boxOrigin, size()); if (locationInContainer.intersects(rect)) { renderer().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToRectBasedTestResult(&renderer().textNode(), request, locationInContainer, rect)) return true; } } } return false; }
bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit, HitTestAction) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, renderer().style().pointerEvents()); bool isVisible = renderer().style().visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if ((hitRules.canHitStroke && (renderer().style().svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (renderer().style().svgStyle().hasFill() || !hitRules.requireFill))) { FloatPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); FloatRect rect(boxOrigin, size()); if (locationInContainer.intersects(rect)) { float scalingFactor = renderer().scalingFactor(); ASSERT(scalingFactor); float baseline = renderer().scaledFont().fontMetrics().floatAscent() / scalingFactor; AffineTransform fragmentTransform; for (auto& fragment : m_textFragments) { FloatQuad fragmentQuad(FloatRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height)); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) fragmentQuad = fragmentTransform.mapQuad(fragmentQuad); if (fragmentQuad.containsPoint(locationInContainer.point())) { renderer().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToRectBasedTestResult(&renderer().textNode(), request, locationInContainer, rect)) return true; } } } } } return false; }
// Hit Testing bool RenderRegion::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { if (!isValid()) return false; LayoutPoint adjustedLocation = accumulatedOffset + location(); // Check our bounds next. For this purpose always assume that we can only be hit in the // foreground phase (which is true for replaced elements like images). // FIXME: Once we support overflow, we need to intersect with that and not with the bounds rect. LayoutRect boundsRect = borderBoxRectInRegion(locationInContainer.region()); boundsRect.moveBy(adjustedLocation); if (visibleToHitTestRequest(request) && action == HitTestForeground && locationInContainer.intersects(boundsRect)) { // Check the contents of the RenderFlowThread. if (m_flowThread->hitTestFlowThreadPortionInRegion(this, flowThreadPortionRect(), flowThreadPortionOverflowRect(), request, result, locationInContainer, LayoutPoint(adjustedLocation.x() + borderLeft() + paddingLeft(), adjustedLocation.y() + borderTop() + paddingTop()))) return true; updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); if (!result.addNodeToRectBasedTestResult(generatingNode(), request, locationInContainer, boundsRect)) return true; } return false; }
bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { LayoutPoint pointInParent = locationInContainer.point() - toLayoutSize(accumulatedOffset); LayoutPoint pointInBorderBox = pointInParent - toLayoutSize(location()); // Only test SVG content if the point is in our content box. // FIXME: This should be an intersection when rect-based hit tests are supported by nodeAtFloatPoint. if (contentBoxRect().contains(pointInBorderBox)) { FloatPoint localPoint = localToParentTransform().inverse().mapPoint(FloatPoint(pointInParent)); for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet. if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) { updateHitTestResult(result, pointInBorderBox); if (!result.addNodeToRectBasedTestResult(child->node(), request, locationInContainer)) return true; } } } // If we didn't early exit above, we've just hit the container <svg> element. Unlike SVG 1.1, 2nd Edition allows container elements to be hit. if (hitTestAction == HitTestBlockBackground && visibleToHitTesting()) { // Only return true here, if the last hit testing phase 'BlockBackground' is executed. If we'd return true in the 'Foreground' phase, // hit testing would stop immediately. For SVG only trees this doesn't matter. Though when we have a <foreignObject> subtree we need // to be able to detect hits on the background of a <div> element. If we'd return true here in the 'Foreground' phase, we are not able // to detect these hits anymore. LayoutRect boundsRect(accumulatedOffset + location(), size()); if (locationInContainer.intersects(boundsRect)) { updateHitTestResult(result, pointInBorderBox); if (!result.addNodeToRectBasedTestResult(&svgSVGElement(), request, locationInContainer, boundsRect)) return true; } } return false; }
bool ClipRect::intersects(const HitTestLocation& hitTestLocation) const { return hitTestLocation.intersects(m_rect); }
bool InlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) { if (isLineBreak()) return false; LayoutPoint boxOrigin = locationIncludingFlipping(); boxOrigin.moveBy(accumulatedOffset); LayoutRect rect(boxOrigin, size()); // FIXME: both calls to rawValue() below is temporary and should be removed once the transition // to LayoutUnit-based types is complete (crbug.com/321237) if (m_truncation != cFullTruncation && visibleToHitTestRequest(result.hitTestRequest()) && locationInContainer.intersects(rect)) { lineLayoutItem().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset))); if (!result.addNodeToListBasedTestResult(lineLayoutItem().node(), locationInContainer, rect)) return true; } return false; }