[Qt] Decide when to apply a scrolled position to the viewport based on the rect cover...
[WebKit-https.git] / Source / WebKit2 / UIProcess / PageViewportController.cpp
1 /*
2  * Copyright (C) 2011, 2012 Nokia Corporation and/or its subsidiary(-ies)
3  * Copyright (C) 2011 Benjamin Poulain <benjamin@webkit.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this program; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  */
21
22 #include "config.h"
23 #include "PageViewportController.h"
24
25 #include "PageViewportControllerClient.h"
26 #include "WebPageProxy.h"
27 #include <WebCore/FloatRect.h>
28 #include <WebCore/FloatSize.h>
29 #include <wtf/MathExtras.h>
30
31 using namespace WebCore;
32
33 namespace WebKit {
34
35 bool fuzzyCompare(float a, float b, float epsilon)
36 {
37     return std::abs(a - b) < epsilon;
38 }
39
40 ViewportUpdateDeferrer::ViewportUpdateDeferrer(PageViewportController* PageViewportController, SuspendContentFlag suspendContentFlag)
41     : m_controller(PageViewportController)
42 {
43     m_controller->m_activeDeferrerCount++;
44
45     // There is no need to suspend content for immediate updates
46     // only during animations or longer gestures.
47     if (suspendContentFlag == DeferUpdateAndSuspendContent)
48         m_controller->suspendContent();
49 }
50
51 ViewportUpdateDeferrer::~ViewportUpdateDeferrer()
52 {
53     if (--(m_controller->m_activeDeferrerCount))
54         return;
55
56     m_controller->resumeContent();
57
58     // Make sure that tiles all around the viewport will be requested.
59     m_controller->syncVisibleContents();
60 }
61
62 PageViewportController::PageViewportController(WebKit::WebPageProxy* proxy, PageViewportControllerClient* client)
63     : m_webPageProxy(proxy)
64     , m_client(client)
65     , m_allowsUserScaling(false)
66     , m_minimumScaleToFit(1)
67     , m_activeDeferrerCount(0)
68     , m_hasSuspendedContent(false)
69     , m_hadUserInteraction(false)
70     , m_effectiveScale(1)
71 {
72     // Initializing Viewport Raw Attributes to avoid random negative scale factors
73     // if there is a race condition between the first layout and setting the viewport attributes for the first time.
74     m_rawAttributes.initialScale = 1;
75     m_rawAttributes.minimumScale = 1;
76     m_rawAttributes.maximumScale = 1;
77     m_rawAttributes.userScalable = m_allowsUserScaling;
78
79     ASSERT(m_client);
80     m_client->setController(this);
81 }
82
83 float PageViewportController::innerBoundedViewportScale(float viewportScale) const
84 {
85     return clampTo(viewportScale, toViewportScale(m_minimumScaleToFit), toViewportScale(m_rawAttributes.maximumScale));
86 }
87
88 float PageViewportController::outerBoundedViewportScale(float viewportScale) const
89 {
90     if (m_allowsUserScaling) {
91         // Bounded by [0.1, 10.0] like the viewport meta code in WebCore.
92         float hardMin = toViewportScale(std::max<float>(0.1, 0.5 * m_minimumScaleToFit));
93         float hardMax = toViewportScale(std::min<float>(10, 2 * m_rawAttributes.maximumScale));
94         return clampTo(viewportScale, hardMin, hardMax);
95     }
96     return innerBoundedViewportScale(viewportScale);
97 }
98
99 float PageViewportController::devicePixelRatio() const
100 {
101     return m_webPageProxy->deviceScaleFactor();
102 }
103
104 FloatPoint PageViewportController::clampViewportToContents(const WebCore::FloatPoint& viewportPos, float viewportScale)
105 {
106     const float horizontalRange = std::max(0.f, m_contentsSize.width() - m_viewportSize.width() / viewportScale);
107     const float verticalRange = std::max(0.f, m_contentsSize.height() - m_viewportSize.height() / viewportScale);
108
109     return FloatPoint(clampTo(viewportPos.x(), 0.f, horizontalRange), clampTo(viewportPos.y(), 0.f, verticalRange));
110 }
111
112 void PageViewportController::didCommitLoad()
113 {
114     // Do not count the previous committed page contents as covered.
115     m_lastFrameCoveredRect = FloatRect();
116
117     // Reset the position to the top, page/history scroll requests may override this before we re-enable rendering.
118     applyPositionAfterRenderingContents(FloatPoint());
119 }
120
121 void PageViewportController::didChangeContentsSize(const IntSize& newSize)
122 {
123     m_contentsSize = newSize;
124     updateMinimumScaleToFit();
125 }
126
127 void PageViewportController::didRenderFrame(const IntSize& contentsSize, const IntRect& coveredRect)
128 {
129     // Only update the viewport's contents dimensions along with its render.
130     m_client->didChangeContentsSize(contentsSize);
131
132     m_lastFrameCoveredRect = coveredRect;
133
134     // Apply any scale or scroll position we locked to be set on the viewport
135     // only when there is something to display there. The scale goes first to
136     // avoid offsetting our deferred position by scaling at the viewport center.
137     // All position and scale changes resulting from a web process event should
138     // go through here to be applied on the viewport to avoid showing incomplete
139     // tiles to the user during a few milliseconds.
140     ViewportUpdateDeferrer guard(this);
141     if (m_effectiveScaleIsLocked) {
142         m_client->setContentsScale(m_effectiveScale, false);
143         m_effectiveScaleIsLocked = false;
144     }
145     if (m_viewportPosIsLocked) {
146         FloatPoint clampedPos = clampViewportToContents(m_viewportPos, m_effectiveScale);
147         // There might be rendered frames not covering our requested position yet, wait for it.
148         if (FloatRect(clampedPos, m_viewportSize / m_effectiveScale).intersects(coveredRect)) {
149             m_client->setViewportPosition(clampedPos);
150             m_viewportPosIsLocked = false;
151         }
152     }
153 }
154
155 void PageViewportController::pageTransitionViewportReady()
156 {
157     if (!m_rawAttributes.layoutSize.isEmpty()) {
158         m_hadUserInteraction = false;
159         applyScaleAfterRenderingContents(innerBoundedViewportScale(toViewportScale(m_rawAttributes.initialScale)));
160     }
161
162     // At this point we should already have received the first viewport arguments and the requested scroll
163     // position for the newly loaded page and sent our reactions to the web process. It's now safe to tell
164     // the web process to start rendering the new page contents and possibly re-use the current tiles.
165     // This assumes that all messages have been handled in order and that nothing has been pushed back on the event loop.
166     m_webPageProxy->commitPageTransitionViewport();
167 }
168
169 void PageViewportController::pageDidRequestScroll(const IntPoint& cssPosition)
170 {
171     // Ignore the request if suspended. Can only happen due to delay in event delivery.
172     if (m_activeDeferrerCount)
173         return;
174
175     FloatRect endVisibleContentRect(clampViewportToContents(cssPosition, m_effectiveScale), m_viewportSize / m_effectiveScale);
176     if (m_lastFrameCoveredRect.intersects(endVisibleContentRect))
177         m_client->setViewportPosition(endVisibleContentRect.location());
178     else
179         // Keep the unclamped position in case the contents size is changed later on.
180         applyPositionAfterRenderingContents(cssPosition);
181 }
182
183 void PageViewportController::didChangeViewportSize(const FloatSize& newSize)
184 {
185     if (newSize.isEmpty())
186         return;
187
188     m_viewportSize = newSize;
189
190     // Let the WebProcess know about the new viewport size, so that
191     // it can resize the content accordingly.
192     m_webPageProxy->setViewportSize(roundedIntSize(newSize));
193
194     syncVisibleContents();
195 }
196
197 void PageViewportController::didChangeContentsVisibility(const FloatPoint& viewportPos, float viewportScale, const FloatPoint& trajectoryVector)
198 {
199     if (!m_viewportPosIsLocked)
200         m_viewportPos = viewportPos;
201     if (!m_effectiveScaleIsLocked)
202         m_effectiveScale = viewportScale;
203
204     syncVisibleContents(trajectoryVector);
205 }
206
207 void PageViewportController::syncVisibleContents(const FloatPoint& trajectoryVector)
208 {
209     DrawingAreaProxy* const drawingArea = m_webPageProxy->drawingArea();
210     if (!drawingArea || m_viewportSize.isEmpty() || m_contentsSize.isEmpty())
211         return;
212
213     FloatRect visibleContentsRect(clampViewportToContents(m_viewportPos, m_effectiveScale), m_viewportSize / m_effectiveScale);
214     visibleContentsRect.intersect(FloatRect(FloatPoint::zero(), m_contentsSize));
215     drawingArea->setVisibleContentsRect(visibleContentsRect, m_effectiveScale, trajectoryVector);
216
217     m_client->didChangeVisibleContents();
218 }
219
220 void PageViewportController::didChangeViewportAttributes(const WebCore::ViewportAttributes& newAttributes)
221 {
222     if (newAttributes.layoutSize.isEmpty())
223         return;
224
225     m_rawAttributes = newAttributes;
226     WebCore::restrictScaleFactorToInitialScaleIfNotUserScalable(m_rawAttributes);
227
228     m_allowsUserScaling = !!m_rawAttributes.userScalable;
229     updateMinimumScaleToFit();
230
231     m_client->didChangeViewportAttributes();
232 }
233
234 void PageViewportController::suspendContent()
235 {
236     if (m_hasSuspendedContent)
237         return;
238
239     m_hasSuspendedContent = true;
240     m_webPageProxy->suspendActiveDOMObjectsAndAnimations();
241 }
242
243 void PageViewportController::resumeContent()
244 {
245     m_client->didResumeContent();
246
247     if (!m_hasSuspendedContent)
248         return;
249
250     m_hasSuspendedContent = false;
251     m_webPageProxy->resumeActiveDOMObjectsAndAnimations();
252 }
253
254 void PageViewportController::applyScaleAfterRenderingContents(float scale)
255 {
256     m_effectiveScale = scale;
257     m_effectiveScaleIsLocked = true;
258     syncVisibleContents();
259 }
260
261 void PageViewportController::applyPositionAfterRenderingContents(const FloatPoint& pos)
262 {
263     m_viewportPos = pos;
264     m_viewportPosIsLocked = true;
265     syncVisibleContents();
266 }
267
268 void PageViewportController::updateMinimumScaleToFit()
269 {
270     if (m_viewportSize.isEmpty())
271         return;
272
273     float minimumScale = WebCore::computeMinimumScaleFactorForContentContained(m_rawAttributes, WebCore::roundedIntSize(m_viewportSize), WebCore::roundedIntSize(m_contentsSize));
274
275     if (!fuzzyCompare(minimumScale, m_minimumScaleToFit, 0.001)) {
276         m_minimumScaleToFit = minimumScale;
277
278         if (!m_hadUserInteraction && !hasSuspendedContent())
279             applyScaleAfterRenderingContents(toViewportScale(minimumScale));
280
281         m_client->didChangeViewportAttributes();
282     }
283 }
284
285 } // namespace WebKit