bool RenderLineBoxList::hitTest(RenderBoxModelObject* renderer, const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) const { ASSERT(renderer->isRenderBlock() || (renderer->isRenderInline() && renderer->hasLayer())); // The only way an inline could hit test like this is if it has a layer. // If we have no lines then we have no work to do. if (!firstLineBox()) return false; LayoutPoint point = locationInContainer.point(); LayoutRect rect = firstLineBox()->isHorizontal() ? IntRect(point.x(), point.y() - locationInContainer.topPadding(), 1, locationInContainer.topPadding() + locationInContainer.bottomPadding() + 1) : IntRect(point.x() - locationInContainer.leftPadding(), point.y(), locationInContainer.rightPadding() + locationInContainer.leftPadding() + 1, 1); if (!anyLineIntersectsRect(renderer, rect, accumulatedOffset)) return false; // See if our root lines contain the point. If so, then we hit test // them further. Note that boxes can easily overlap, so we can't make any assumptions // based off positions of our first line box or our last line box. for (InlineFlowBox* curr = lastLineBox(); curr; curr = curr->prevLineBox()) { const RootInlineBox& rootBox = curr->root(); if (rangeIntersectsRect(renderer, curr->logicalTopVisualOverflow(rootBox.lineTop()), curr->logicalBottomVisualOverflow(rootBox.lineBottom()), rect, accumulatedOffset)) { bool inside = curr->nodeAtPoint(request, result, locationInContainer, accumulatedOffset, rootBox.lineTop(), rootBox.lineBottom(), hitTestAction); if (inside) { renderer->updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); return true; } } } return false; }
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 RenderView::hitTest(const HitTestRequest& request, const HitTestLocation& location, HitTestResult& result) { TRACE_EVENT0("blink", "RenderView::hitTest"); m_hitTestCount++; if (!m_frameView->visibleContentRect().contains(location.roundedPoint())) return false; // We have to recursively update layout/style here because otherwise, when the hit test recurses // into a child document, it could trigger a layout on the parent document, which can destroy RenderLayers // that are higher up in the call stack, leading to crashes. // Note that Document::updateLayout calls its parent's updateLayout. // FIXME: It should be the caller's responsibility to ensure an up-to-date layout. frameView()->updateLayoutAndStyleIfNeededRecursive(); // RenderView should make sure to update layout before entering hit testing ASSERT(!frame()->view()->layoutPending()); ASSERT(!document().renderView()->needsLayout()); // TODO(ojan): Does any of this intersection stuff make sense for Sky? LayoutRect hitTestArea = view()->documentRect(); if (!request.ignoreClipping()) hitTestArea.intersect(frame()->view()->visibleContentRect()); bool insideLayer = hitTestLayer(layer(), 0, request, result, hitTestArea, location); if (!insideLayer) { // TODO(ojan): Is this code needed for Sky? // We didn't hit any layer. If we are the root layer and the mouse is -- or just was -- down, // return ourselves. We do this so mouse events continue getting delivered after a drag has // exited the WebView, and so hit testing over a scrollbar hits the content document. if (request.active() || request.release()) { updateHitTestResult(result, location.point()); insideLayer = true; } } return insideLayer; }
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; }
bool InlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) { if (isLineBreak() || m_truncation == cFullTruncation) return false; LayoutPoint boxOrigin = physicalLocation(); 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; }
// 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 EllipsisBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit lineTop, LayoutUnit lineBottom, HitTestAction hitTestAction) { LayoutPoint adjustedLocation = accumulatedOffset + LayoutPoint(topLeft()); // Hit test the markup box. if (InlineBox* markupBox = this->markupBox()) { const RenderStyle& lineStyle = this->lineStyle(); LayoutUnit mtx = adjustedLocation.x() + m_logicalWidth - markupBox->x(); LayoutUnit mty = adjustedLocation.y() + lineStyle.fontMetrics().ascent() - (markupBox->y() + markupBox->lineStyle().fontMetrics().ascent()); if (markupBox->nodeAtPoint(request, result, locationInContainer, LayoutPoint(mtx, mty), lineTop, lineBottom, hitTestAction)) { blockFlow().updateHitTestResult(result, locationInContainer.point() - LayoutSize(mtx, mty)); return true; } } LayoutRect boundsRect(adjustedLocation, LayoutSize(m_logicalWidth, m_height)); if (visibleToHitTesting() && boundsRect.intersects(HitTestLocation::rectForPoint(locationInContainer.point(), 0, 0, 0, 0))) { blockFlow().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); if (!result.addNodeToRectBasedTestResult(blockFlow().element(), 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 HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const FloatRect& rect) { // If it is not a rect-based hit test, this method has to be no-op. // Return false, so the hit test stops. if (!isRectBasedTest()) return false; // If node is null, return true so the hit test can continue. if (!node) return true; mutableRectBasedTestResult().add(node); bool regionFilled = rect.contains(locationInContainer.boundingBox()); return !regionFilled; }
ListBasedHitTestBehavior HitTestResult::addNodeToListBasedTestResult(Node* node, const HitTestLocation& location, const Region& region) { // If not a list-based test, stop testing because the hit has been found. if (!hitTestRequest().listBased()) return StopHitTesting; if (!node) return ContinueHitTesting; mutableListBasedTestResult().add(node); if (hitTestRequest().penetratingList()) return ContinueHitTesting; return region.contains(location.boundingBox()) ? StopHitTesting : ContinueHitTesting; }
bool EllipsisBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit lineTop, LayoutUnit lineBottom) { // FIXME: the call to roundedLayoutPoint() below is temporary and should be removed once // the transition to LayoutUnit-based types is complete (crbug.com/321237) LayoutPoint adjustedLocation = accumulatedOffset + topLeft(); LayoutPoint boxOrigin = locationIncludingFlipping(); boxOrigin.moveBy(accumulatedOffset); LayoutRect boundsRect(boxOrigin, size()); if (visibleToHitTestRequest(result.hitTestRequest()) && boundsRect.intersects(LayoutRect(HitTestLocation::rectForPoint(locationInContainer.point(), 0, 0, 0, 0)))) { lineLayoutItem().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); if (result.addNodeToListBasedTestResult(lineLayoutItem().node(), locationInContainer, boundsRect) == StopHitTesting) return true; } return false; }
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; }
bool RenderView::hitTest(const HitTestRequest& request, const HitTestLocation& location, HitTestResult& result) { if (layer()->hitTest(request, location, result)) return true; // FIXME: Consider if this test should be done unconditionally. if (request.allowsFrameScrollbars()) { // ScrollView scrollbars are not the same as RenderLayer scrollbars tested by RenderLayer::hitTestOverflowControls, // so we need to test ScrollView scrollbars separately here. Scrollbar* frameScrollbar = frameView().scrollbarAtPoint(location.roundedPoint()); if (frameScrollbar) { result.setScrollbar(frameScrollbar); return true; } } return false; }
bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const LayoutRect& rect) { // If it is not a rect-based hit test, this method has to be no-op. // Return false, so the hit test stops. if (!isRectBasedTest()) return false; // If node is null, return true so the hit test can continue. if (!node) return true; if (request.disallowsShadowContent()) node = node->document()->ancestorInThisScope(node); mutableRectBasedTestResult().add(node); bool regionFilled = rect.contains(locationInContainer.boundingBox()); return !regionFilled; }
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); }
// Hit Testing bool RenderTableRow::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { // Table rows cannot ever be hit tested. Effectively they do not exist. // Just forward to our children always. for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { // FIXME: We have to skip over inline flows, since they can show up inside table rows // at the moment (a demoted inline <form> for example). If we ever implement a // table-specific hit-test method (which we should do for performance reasons anyway), // then we can remove this check. if (child->isTableCell() && !toRenderBox(child)->hasSelfPaintingLayer()) { LayoutPoint cellPoint = flipForWritingModeForChild(toRenderTableCell(child), accumulatedOffset); if (child->nodeAtPoint(request, result, locationInContainer, cellPoint, action)) { updateHitTestResult(result, locationInContainer.point() - toLayoutSize(cellPoint)); return true; } } } return false; }
bool RenderTextControlSingleLine::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { if (!RenderTextControl::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction)) return false; // Say that we hit the inner text element if // - we hit a node inside the inner text element, // - we hit the <input> element (e.g. we're over the border or padding), or // - we hit regions not in any decoration buttons. HTMLElement* container = containerElement(); if (result.innerNode()->isDescendantOf(innerTextElement()) || result.innerNode() == &inputElement() || (container && container == result.innerNode())) { LayoutPoint pointInParent = locationInContainer.point(); if (container && innerBlockElement()) { if (innerBlockElement()->renderBox()) pointInParent -= toLayoutSize(innerBlockElement()->renderBox()->location()); if (container->renderBox()) pointInParent -= toLayoutSize(container->renderBox()->location()); } hitInnerTextElement(result, pointInParent, accumulatedOffset); } return true; }
bool RenderView::hitTest(const HitTestRequest& request, const HitTestLocation& location, HitTestResult& result) { TRACE_EVENT0("blink", "RenderView::hitTest"); m_hitTestCount++; // We have to recursively update layout/style here because otherwise, when the hit test recurses // into a child document, it could trigger a layout on the parent document, which can destroy RenderLayers // that are higher up in the call stack, leading to crashes. // Note that Document::updateLayout calls its parent's updateLayout. // FIXME: It should be the caller's responsibility to ensure an up-to-date layout. frameView()->updateLayoutAndStyleIfNeededRecursive(); bool hitLayer = layer()->hitTest(request, location, result); // FrameView scrollbars are not the same as RenderLayer scrollbars tested by RenderLayer::hitTestOverflowControls, // so we need to test FrameView scrollbars separately here. Note that it's important we do this after // the hit test above, because that may overwrite the entire HitTestResult when it finds a hit. IntPoint viewPoint = location.roundedPoint() - frameView()->scrollOffset(); if (Scrollbar* frameScrollbar = frameView()->scrollbarAtViewPoint(viewPoint)) result.setScrollbar(frameScrollbar); return hitLayer; }
bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { HitTestResult tempResult(result.hitTestLocation()); bool inside = RenderReplaced::nodeAtPoint(request, tempResult, locationInContainer, accumulatedOffset, hitTestAction); if (tempResult.innerNode() && node()) { if (HTMLMapElement* map = imageMap()) { LayoutRect contentBox = contentBoxRect(); float scaleFactor = 1 / style()->effectiveZoom(); LayoutPoint mapLocation = locationInContainer.point() - toLayoutSize(accumulatedOffset) - locationOffset() - toLayoutSize(contentBox.location()); mapLocation.scale(scaleFactor, scaleFactor); if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult)) tempResult.setInnerNonSharedNode(node()); } } if (!inside && result.isRectBasedTest()) result.append(tempResult); if (inside) result = tempResult; return inside; }
// Hit Testing bool LayoutTableRow::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { // Table rows cannot ever be hit tested. Effectively they do not exist. // Just forward to our children always. for (LayoutTableCell* cell = lastCell(); cell; cell = cell->previousCell()) { // FIXME: We have to skip over inline flows, since they can show up inside // table rows at the moment (a demoted inline <form> for example). If we // ever implement a table-specific hit-test method (which we should do for // performance reasons anyway), then we can remove this check. if (!cell->hasSelfPaintingLayer()) { LayoutPoint cellPoint = flipForWritingModeForChild(cell, accumulatedOffset); if (cell->nodeAtPoint(result, locationInContainer, cellPoint, action)) { updateHitTestResult( result, locationInContainer.point() - toLayoutSize(cellPoint)); return true; } } } return false; }
bool ClipRect::intersects(const HitTestLocation& hitTestLocation) const { return hitTestLocation.intersects(m_rect); }