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
51namespace WebKit {
52using namespace WebCore;
53
54#if PLATFORM(IOS_FAMILY)
55static const double minimumScaleDifferenceForZooming = 0.3;
56#endif
57
58ViewGestureGeometryCollector::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
67ViewGestureGeometryCollector::~ViewGestureGeometryCollector()
68{
69 WebProcess::singleton().removeMessageReceiver(Messages::ViewGestureGeometryCollector::messageReceiverName(), m_webPage.pageID());
70}
71
72void 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
82void 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
134struct FontSizeAndCount {
135 unsigned fontSize;
136 unsigned count;
137};
138
139Optional<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
216void 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
236void 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)
248void 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)
257void ViewGestureGeometryCollector::setRenderTreeSizeNotificationThreshold(uint64_t size)
258{
259 m_renderTreeSizeNotificationThreshold = size;
260 sendDidHitRenderTreeSizeThresholdIfNeeded();
261}
262
263void 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
272void 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