2 * Copyright (C) 2022 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #include "InteractionRegion.h"
31 #include "FrameSnapshotting.h"
32 #include "FrameView.h"
33 #include "GeometryUtilities.h"
34 #include "HTMLAnchorElement.h"
35 #include "HTMLFormControlElement.h"
36 #include "HitTestResult.h"
38 #include "PathUtilities.h"
39 #include "PlatformMouseEvent.h"
40 #include "RenderBox.h"
41 #include "SimpleRange.h"
42 #include <wtf/NeverDestroyed.h>
46 InteractionRegion::~InteractionRegion() = default;
48 static FloatRect absoluteBoundingRectForRange(const SimpleRange& range)
50 return unionRectIgnoringZeroRects(RenderObject::absoluteBorderAndTextRects(range, {
51 RenderObject::BoundingRectBehavior::RespectClipping,
52 RenderObject::BoundingRectBehavior::UseVisibleBounds,
53 RenderObject::BoundingRectBehavior::IgnoreTinyRects,
57 static std::optional<InteractionRegion> regionForElement(Element& element)
59 auto& frameView = *element.document().frame()->view();
60 auto& mainFrameView = *element.document().frame()->mainFrame().view();
62 IntRect frameClipRect;
63 #if PLATFORM(IOS_FAMILY)
64 frameClipRect = enclosingIntRect(frameView.exposedContentRect());
66 if (auto viewExposedRect = frameView.viewExposedRect())
67 frameClipRect = enclosingIntRect(*viewExposedRect);
69 frameClipRect = frameView.visibleContentRect();
72 auto* renderer = element.renderer();
76 Vector<FloatRect> rectsInContentCoordinates;
77 InteractionRegion region;
78 auto linkRange = makeRangeSelectingNode(element);
81 region.hasLightBackground = estimatedBackgroundColorForRange(*linkRange, *element.document().frame()).luminance() > 0.5;
83 if (linkRange && renderer->isInline() && !renderer->isReplacedOrInlineBlock()) {
84 static constexpr float inlinePadding = 3;
85 OptionSet<RenderObject::BoundingRectBehavior> behavior { RenderObject::BoundingRectBehavior::RespectClipping };
86 rectsInContentCoordinates = RenderObject::absoluteTextRects(*linkRange, behavior).map([&](auto rect) -> FloatRect {
87 rect.inflate(inlinePadding);
91 if (rectsInContentCoordinates.isEmpty()) {
92 auto boundingRectForRange = absoluteBoundingRectForRange(*linkRange);
93 if (!boundingRectForRange.isEmpty()) {
94 boundingRectForRange.inflate(inlinePadding);
95 rectsInContentCoordinates = { boundingRectForRange };
100 if (rectsInContentCoordinates.isEmpty())
101 rectsInContentCoordinates = { renderer->absoluteBoundingBoxRect() };
103 auto layoutArea = mainFrameView.layoutSize().area();
104 rectsInContentCoordinates = compactMap(rectsInContentCoordinates, [&] (auto rect) -> std::optional<FloatRect> {
105 if (rect.area() > layoutArea / 2)
110 if (is<RenderBox>(*renderer)) {
111 RoundedRect::Radii borderRadii = downcast<RenderBox>(*renderer).borderRadii();
112 region.borderRadius = borderRadii.minimumRadius();
115 region.rectsInContentCoordinates = compactMap(rectsInContentCoordinates, [&](auto rect) -> std::optional<FloatRect> {
116 auto contentsRect = rect;
118 if (&frameView != &mainFrameView)
119 contentsRect.intersect(frameClipRect);
121 if (contentsRect.isEmpty())
127 if (region.rectsInContentCoordinates.isEmpty())
133 static CursorType cursorTypeForElement(Element& element)
135 auto* renderer = element.renderer();
136 auto* style = renderer ? &renderer->style() : nullptr;
137 auto cursorType = style ? style->cursor() : CursorType::Auto;
139 if (cursorType == CursorType::Auto && element.enclosingLinkEventParentOrSelf() && element.isLink())
140 cursorType = CursorType::Pointer;
145 Vector<InteractionRegion> interactionRegions(Page& page, FloatRect rect)
147 Ref frame(page.mainFrame());
148 RefPtr frameView = frame->view();
153 frameView->updateLayoutAndStyleIfNeededRecursive();
155 RefPtr document = frame->document();
159 auto result = HitTestResult { LayoutRect(rect) };
160 HitTestRequest request({
161 HitTestRequest::Type::ReadOnly,
162 HitTestRequest::Type::Active,
163 HitTestRequest::Type::AllowVisibleChildFrameContentOnly,
164 HitTestRequest::Type::CollectMultipleElements
166 document->hitTest(request, result);
168 Vector<InteractionRegion> regions;
170 for (const auto& node : result.listBasedTestResult()) {
171 if (!is<Element>(node.get()))
173 auto& element = downcast<Element>(node.get());
175 if (!element.willRespondToMouseClickEvents() && !element.willRespondToTouchEvents())
178 if (cursorTypeForElement(element) != CursorType::Pointer && !is<HTMLFormControlElement>(element))
181 auto region = regionForElement(element);
183 regions.append(*region);