1 | /* |
2 | * Copyright (C) 2013, 2014 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
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. |
12 | * |
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. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "ViewGestureGeometryCollector.h" |
28 | |
29 | #include "Logging.h" |
30 | #include "ViewGestureGeometryCollectorMessages.h" |
31 | #include "WebCoreArgumentCoders.h" |
32 | #include "WebFrame.h" |
33 | #include "WebPage.h" |
34 | #include "WebProcess.h" |
35 | #include <WebCore/FontCascade.h> |
36 | #include <WebCore/Frame.h> |
37 | #include <WebCore/FrameView.h> |
38 | #include <WebCore/HTMLImageElement.h> |
39 | #include <WebCore/HTMLTextFormControlElement.h> |
40 | #include <WebCore/HitTestResult.h> |
41 | #include <WebCore/ImageDocument.h> |
42 | #include <WebCore/RenderView.h> |
43 | #include <WebCore/TextIterator.h> |
44 | |
45 | #if PLATFORM(IOS_FAMILY) |
46 | #include "SmartMagnificationControllerMessages.h" |
47 | #else |
48 | #include "ViewGestureControllerMessages.h" |
49 | #endif |
50 | |
51 | namespace WebKit { |
52 | using namespace WebCore; |
53 | |
54 | #if PLATFORM(IOS_FAMILY) |
55 | static const double minimumScaleDifferenceForZooming = 0.3; |
56 | #endif |
57 | |
58 | ViewGestureGeometryCollector::ViewGestureGeometryCollector(WebPage& webPage) |
59 | : m_webPage(webPage) |
60 | #if !PLATFORM(IOS_FAMILY) |
61 | , m_renderTreeSizeNotificationThreshold(0) |
62 | #endif |
63 | { |
64 | WebProcess::singleton().addMessageReceiver(Messages::ViewGestureGeometryCollector::messageReceiverName(), m_webPage.pageID(), *this); |
65 | } |
66 | |
67 | ViewGestureGeometryCollector::~ViewGestureGeometryCollector() |
68 | { |
69 | WebProcess::singleton().removeMessageReceiver(Messages::ViewGestureGeometryCollector::messageReceiverName(), m_webPage.pageID()); |
70 | } |
71 | |
72 | void ViewGestureGeometryCollector::dispatchDidCollectGeometryForSmartMagnificationGesture(FloatPoint origin, FloatRect targetRect, FloatRect visibleContentRect, bool fitEntireRect, double viewportMinimumScale, double viewportMaximumScale) |
73 | { |
74 | #if PLATFORM(MAC) |
75 | m_webPage.send(Messages::ViewGestureController::DidCollectGeometryForSmartMagnificationGesture(origin, targetRect, visibleContentRect, fitEntireRect, viewportMinimumScale, viewportMaximumScale)); |
76 | #endif |
77 | #if PLATFORM(IOS_FAMILY) |
78 | m_webPage.send(Messages::SmartMagnificationController::DidCollectGeometryForSmartMagnificationGesture(origin, targetRect, visibleContentRect, fitEntireRect, viewportMinimumScale, viewportMaximumScale)); |
79 | #endif |
80 | } |
81 | |
82 | void ViewGestureGeometryCollector::collectGeometryForSmartMagnificationGesture(FloatPoint origin) |
83 | { |
84 | FloatRect visibleContentRect = m_webPage.mainFrameView()->unobscuredContentRectIncludingScrollbars(); |
85 | |
86 | if (m_webPage.mainWebFrame()->handlesPageScaleGesture()) |
87 | return; |
88 | |
89 | double viewportMinimumScale; |
90 | double viewportMaximumScale; |
91 | |
92 | #if PLATFORM(IOS_FAMILY) |
93 | if (m_webPage.platformPrefersTextLegibilityBasedZoomScaling()) { |
94 | auto textLegibilityScales = computeTextLegibilityScales(viewportMinimumScale, viewportMaximumScale); |
95 | if (!textLegibilityScales) { |
96 | dispatchDidCollectGeometryForSmartMagnificationGesture({ }, { }, { }, false, 0, 0); |
97 | return; |
98 | } |
99 | |
100 | float targetScale = m_webPage.viewportConfiguration().initialScale(); |
101 | float currentScale = m_webPage.pageScaleFactor(); |
102 | if (currentScale < textLegibilityScales->first - minimumScaleDifferenceForZooming) |
103 | targetScale = textLegibilityScales->first; |
104 | else if (currentScale < textLegibilityScales->second - minimumScaleDifferenceForZooming) |
105 | targetScale = textLegibilityScales->second; |
106 | |
107 | FloatRect targetRectInContentCoordinates { origin, FloatSize() }; |
108 | targetRectInContentCoordinates.inflate(m_webPage.viewportConfiguration().viewLayoutSize() / (2 * targetScale)); |
109 | |
110 | dispatchDidCollectGeometryForSmartMagnificationGesture(origin, targetRectInContentCoordinates, visibleContentRect, true, viewportMinimumScale, viewportMaximumScale); |
111 | return; |
112 | } |
113 | #endif // PLATFORM(IOS_FAMILY) |
114 | |
115 | IntPoint originInContentsSpace = m_webPage.mainFrameView()->windowToContents(roundedIntPoint(origin)); |
116 | HitTestResult hitTestResult = HitTestResult(originInContentsSpace); |
117 | |
118 | m_webPage.mainFrame()->document()->hitTest(HitTestRequest(), hitTestResult); |
119 | Node* node = hitTestResult.innerNode(); |
120 | if (!node) { |
121 | dispatchDidCollectGeometryForSmartMagnificationGesture(FloatPoint(), FloatRect(), FloatRect(), false, 0, 0); |
122 | return; |
123 | } |
124 | |
125 | bool isReplaced; |
126 | FloatRect renderRect; |
127 | |
128 | computeZoomInformationForNode(*node, origin, renderRect, isReplaced, viewportMinimumScale, viewportMaximumScale); |
129 | dispatchDidCollectGeometryForSmartMagnificationGesture(origin, renderRect, visibleContentRect, isReplaced, viewportMinimumScale, viewportMaximumScale); |
130 | } |
131 | |
132 | #if PLATFORM(IOS_FAMILY) |
133 | |
134 | struct FontSizeAndCount { |
135 | unsigned fontSize; |
136 | unsigned count; |
137 | }; |
138 | |
139 | Optional<std::pair<double, double>> ViewGestureGeometryCollector::computeTextLegibilityScales(double& viewportMinimumScale, double& viewportMaximumScale) |
140 | { |
141 | static const unsigned fontSizeBinningInterval = 2; |
142 | static const double maximumNumberOfTextRunsToConsider = 200; |
143 | |
144 | static const double targetLegibilityFontSize = 12; |
145 | static const double textLegibilityScaleRatio = 0.1; |
146 | static const double defaultTextLegibilityZoomScale = 1; |
147 | |
148 | computeMinimumAndMaximumViewportScales(viewportMinimumScale, viewportMaximumScale); |
149 | if (m_cachedTextLegibilityScales) |
150 | return m_cachedTextLegibilityScales; |
151 | |
152 | auto document = makeRefPtr(m_webPage.mainFrame()->document()); |
153 | if (!document) |
154 | return WTF::nullopt; |
155 | |
156 | document->updateLayoutIgnorePendingStylesheets(); |
157 | |
158 | auto documentRange = Range::create(*document, {{ document->documentElement(), Position::PositionIsBeforeAnchor }}, {{ document->documentElement(), Position::PositionIsAfterAnchor }}); |
159 | HashSet<Node*> allTextNodes; |
160 | HashMap<unsigned, unsigned> fontSizeToCountMap; |
161 | unsigned numberOfIterations = 0; |
162 | unsigned totalSampledTextLength = 0; |
163 | |
164 | for (TextIterator documentTextIterator { documentRange.ptr(), TextIteratorEntersTextControls }; !documentTextIterator.atEnd(); documentTextIterator.advance()) { |
165 | if (++numberOfIterations >= maximumNumberOfTextRunsToConsider) |
166 | break; |
167 | |
168 | if (!is<Text>(documentTextIterator.node())) |
169 | continue; |
170 | |
171 | auto& textNode = downcast<Text>(*documentTextIterator.node()); |
172 | auto textLength = textNode.length(); |
173 | if (!textLength || !textNode.renderer() || allTextNodes.contains(&textNode)) |
174 | continue; |
175 | |
176 | allTextNodes.add(&textNode); |
177 | |
178 | unsigned fontSizeBin = fontSizeBinningInterval * round(textNode.renderer()->style().fontCascade().size() / fontSizeBinningInterval); |
179 | auto entry = fontSizeToCountMap.find(fontSizeBin); |
180 | fontSizeToCountMap.set(fontSizeBin, textLength + (entry == fontSizeToCountMap.end() ? 0 : entry->value)); |
181 | totalSampledTextLength += textLength; |
182 | } |
183 | |
184 | Vector<FontSizeAndCount> sortedFontSizesAndCounts; |
185 | sortedFontSizesAndCounts.reserveCapacity(fontSizeToCountMap.size()); |
186 | for (auto& entry : fontSizeToCountMap) |
187 | sortedFontSizesAndCounts.append({ entry.key, entry.value }); |
188 | |
189 | std::sort(sortedFontSizesAndCounts.begin(), sortedFontSizesAndCounts.end(), [] (auto& first, auto& second) { |
190 | return first.fontSize < second.fontSize; |
191 | }); |
192 | |
193 | double defaultScale = clampTo<double>(defaultTextLegibilityZoomScale, viewportMinimumScale, viewportMaximumScale); |
194 | double textLegibilityScale = defaultScale; |
195 | double currentSampledTextLength = 0; |
196 | for (auto& fontSizeAndCount : sortedFontSizesAndCounts) { |
197 | currentSampledTextLength += fontSizeAndCount.count; |
198 | double ratioOfTextUnderCurrentFontSize = currentSampledTextLength / totalSampledTextLength; |
199 | if (ratioOfTextUnderCurrentFontSize >= textLegibilityScaleRatio) { |
200 | textLegibilityScale = clampTo<double>(targetLegibilityFontSize / fontSizeAndCount.fontSize, viewportMinimumScale, viewportMaximumScale); |
201 | break; |
202 | } |
203 | } |
204 | |
205 | auto firstTextLegibilityScale = std::min<double>(textLegibilityScale, defaultScale); |
206 | auto secondTextLegibilityScale = std::max<double>(textLegibilityScale, defaultScale); |
207 | if (secondTextLegibilityScale - firstTextLegibilityScale < minimumScaleDifferenceForZooming) |
208 | firstTextLegibilityScale = secondTextLegibilityScale; |
209 | |
210 | m_cachedTextLegibilityScales.emplace(std::pair<double, double> { firstTextLegibilityScale, secondTextLegibilityScale }); |
211 | return m_cachedTextLegibilityScales; |
212 | } |
213 | |
214 | #endif // PLATFORM(IOS_FAMILY) |
215 | |
216 | void ViewGestureGeometryCollector::computeZoomInformationForNode(Node& node, FloatPoint& origin, FloatRect& renderRect, bool& isReplaced, double& viewportMinimumScale, double& viewportMaximumScale) |
217 | { |
218 | renderRect = node.renderRect(&isReplaced); |
219 | if (node.document().isImageDocument()) { |
220 | if (HTMLImageElement* imageElement = static_cast<ImageDocument&>(node.document()).imageElement()) { |
221 | if (&node != imageElement) { |
222 | renderRect = imageElement->renderRect(&isReplaced); |
223 | FloatPoint newOrigin = origin; |
224 | if (origin.x() < renderRect.x() || origin.x() > renderRect.maxX()) |
225 | newOrigin.setX(renderRect.x() + renderRect.width() / 2); |
226 | if (origin.y() < renderRect.y() || origin.y() > renderRect.maxY()) |
227 | newOrigin.setY(renderRect.y() + renderRect.height() / 2); |
228 | origin = newOrigin; |
229 | } |
230 | isReplaced = true; |
231 | } |
232 | } |
233 | computeMinimumAndMaximumViewportScales(viewportMinimumScale, viewportMaximumScale); |
234 | } |
235 | |
236 | void ViewGestureGeometryCollector::computeMinimumAndMaximumViewportScales(double& viewportMinimumScale, double& viewportMaximumScale) const |
237 | { |
238 | #if PLATFORM(IOS_FAMILY) |
239 | viewportMinimumScale = m_webPage.minimumPageScaleFactor(); |
240 | viewportMaximumScale = m_webPage.maximumPageScaleFactor(); |
241 | #else |
242 | viewportMinimumScale = 0; |
243 | viewportMaximumScale = std::numeric_limits<double>::max(); |
244 | #endif |
245 | } |
246 | |
247 | #if PLATFORM(MAC) |
248 | void ViewGestureGeometryCollector::collectGeometryForMagnificationGesture() |
249 | { |
250 | FloatRect visibleContentRect = m_webPage.mainFrameView()->unobscuredContentRectIncludingScrollbars(); |
251 | bool frameHandlesMagnificationGesture = m_webPage.mainWebFrame()->handlesPageScaleGesture(); |
252 | m_webPage.send(Messages::ViewGestureController::DidCollectGeometryForMagnificationGesture(visibleContentRect, frameHandlesMagnificationGesture)); |
253 | } |
254 | #endif |
255 | |
256 | #if !PLATFORM(IOS_FAMILY) |
257 | void ViewGestureGeometryCollector::setRenderTreeSizeNotificationThreshold(uint64_t size) |
258 | { |
259 | m_renderTreeSizeNotificationThreshold = size; |
260 | sendDidHitRenderTreeSizeThresholdIfNeeded(); |
261 | } |
262 | |
263 | void ViewGestureGeometryCollector::sendDidHitRenderTreeSizeThresholdIfNeeded() |
264 | { |
265 | if (m_renderTreeSizeNotificationThreshold && m_webPage.renderTreeSize() >= m_renderTreeSizeNotificationThreshold) { |
266 | m_webPage.send(Messages::ViewGestureController::DidHitRenderTreeSizeThreshold()); |
267 | m_renderTreeSizeNotificationThreshold = 0; |
268 | } |
269 | } |
270 | #endif |
271 | |
272 | void ViewGestureGeometryCollector::mainFrameDidLayout() |
273 | { |
274 | #if PLATFORM(IOS_FAMILY) |
275 | m_cachedTextLegibilityScales.reset(); |
276 | #else |
277 | sendDidHitRenderTreeSizeThresholdIfNeeded(); |
278 | #endif |
279 | } |
280 | |
281 | } // namespace WebKit |
282 | |
283 | |