[iOS] Synchronize the WKContentView and UIScrollView updates with the tiles being...
[WebKit-https.git] / Source / WebCore / page / ViewportConfiguration.cpp
1 /*
2  * Copyright (C) 2005-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 "ViewportConfiguration.h"
28
29 #include <wtf/Assertions.h>
30 #include <wtf/MathExtras.h>
31
32 namespace WebCore {
33
34 #if !ASSERT_DISABLED
35 static bool constraintsAreAllRelative(const ViewportConfiguration::Parameters& configuration)
36 {
37     return !configuration.widthIsSet && !configuration.heightIsSet && !configuration.initialScaleIsSet;
38 }
39 #endif
40
41 ViewportConfiguration::ViewportConfiguration()
42     : m_contentSize(1024, 768)
43     , m_minimumLayoutSize(m_contentSize)
44 {
45     // Setup a reasonable default configuration to avoid computing infinite scale/sizes.
46     // Those are the original iPhone configuration.
47     m_defaultConfiguration.width = 980;
48     m_defaultConfiguration.widthIsSet = true;
49     m_defaultConfiguration.allowsUserScaling = true;
50     m_defaultConfiguration.minimumScale = 0.25;
51     m_defaultConfiguration.maximumScale = 5;
52     updateConfiguration();
53 }
54
55 void ViewportConfiguration::setDefaultConfiguration(const ViewportConfiguration::Parameters& defaultConfiguration)
56 {
57     ASSERT(!constraintsAreAllRelative(m_configuration));
58     ASSERT(!m_defaultConfiguration.initialScaleIsSet || defaultConfiguration.initialScale > 0);
59     ASSERT(defaultConfiguration.minimumScale > 0);
60     ASSERT(defaultConfiguration.maximumScale >= defaultConfiguration.minimumScale);
61
62     m_defaultConfiguration = defaultConfiguration;
63     updateConfiguration();
64 }
65
66 void ViewportConfiguration::setContentsSize(const IntSize& contentSize)
67 {
68     if (m_contentSize == contentSize)
69         return;
70
71     m_contentSize = contentSize;
72     updateConfiguration();
73 }
74
75 void ViewportConfiguration::setMinimumLayoutSize(const IntSize& minimumLayoutSize)
76 {
77     if (m_minimumLayoutSize == minimumLayoutSize)
78         return;
79
80     m_minimumLayoutSize = minimumLayoutSize;
81     updateConfiguration();
82 }
83
84 void ViewportConfiguration::setViewportArguments(const ViewportArguments& viewportArguments)
85 {
86     if (m_viewportArguments == viewportArguments)
87         return;
88
89     m_viewportArguments = viewportArguments;
90     updateConfiguration();
91 }
92
93 IntSize ViewportConfiguration::layoutSize() const
94 {
95     return IntSize(layoutWidth(), layoutHeight());
96 }
97
98 double ViewportConfiguration::initialScale() const
99 {
100     ASSERT(!constraintsAreAllRelative(m_configuration));
101
102     // If the document has specified its own initial scale, use it regardless.
103     // This is guaranteed to be sanity checked already, so no need for MIN/MAX.
104     if (m_configuration.initialScaleIsSet)
105         return m_configuration.initialScale;
106
107     // If not, it is up to us to determine the initial scale.
108     // We want a scale small enough to fit the document width-wise.
109     double width = m_contentSize.width() > 0 ? m_contentSize.width() : layoutWidth();
110     double initialScale = 0;
111     if (width > 0)
112         initialScale = m_minimumLayoutSize.width() / width;
113
114     // Prevent the intial scale from shrinking to a height smaller than our view's minimum height.
115     double height = m_contentSize.height() > 0 ? m_contentSize.height() : layoutHeight();
116     if (height > 0 && height * initialScale < m_minimumLayoutSize.height())
117         initialScale = m_minimumLayoutSize.height() / height;
118     return std::min(std::max(initialScale, m_configuration.minimumScale), m_configuration.maximumScale);
119 }
120
121 double ViewportConfiguration::minimumScale() const
122 {
123     // If we scale to fit, then this is our minimum scale as well.
124     if (!m_configuration.initialScaleIsSet)
125         return initialScale();
126
127     // If not, we still need to sanity check our value.
128     double minimumScale = m_configuration.initialScale;
129
130     double contentWidth = m_contentSize.width();
131     if (contentWidth > 0 && contentWidth * minimumScale < m_minimumLayoutSize.width())
132         minimumScale = m_minimumLayoutSize.width() / contentWidth;
133
134     double contentHeight = m_contentSize.height();
135     if (contentHeight > 0 && contentHeight * minimumScale < m_minimumLayoutSize.height())
136         minimumScale = m_minimumLayoutSize.height() / contentHeight;
137
138     minimumScale = std::min(std::max(minimumScale, m_configuration.minimumScale), m_configuration.maximumScale);
139
140     return minimumScale;
141 }
142
143 static inline bool viewportArgumentValueIsValid(float value)
144 {
145     return value > 0;
146 }
147
148 template<typename ValueType, typename ViewportArgumentsType>
149 static inline void applyViewportArgument(ValueType& value, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum)
150 {
151     if (viewportArgumentValueIsValid(viewportArgumentValue))
152         value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue)));
153 }
154
155 template<typename ValueType, typename ViewportArgumentsType>
156 static inline void applyViewportArgument(ValueType& value, bool& valueIsSet, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum)
157 {
158     if (viewportArgumentValueIsValid(viewportArgumentValue)) {
159         value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue)));
160         valueIsSet = true;
161     } else
162         valueIsSet = false;
163 }
164
165 void ViewportConfiguration::updateConfiguration()
166 {
167     m_configuration = m_defaultConfiguration;
168
169     const double minimumViewportArgumentsScaleFactor = 0.1;
170     const double maximumViewportArgumentsScaleFactor = 10.0;
171
172     bool viewportArgumentsOverridesInitialScale;
173     bool viewportArgumentsOverridesWidth;
174     bool viewportArgumentsOverridesHeight;
175
176     applyViewportArgument(m_configuration.minimumScale, m_viewportArguments.minZoom, minimumViewportArgumentsScaleFactor, maximumViewportArgumentsScaleFactor);
177     applyViewportArgument(m_configuration.maximumScale, m_viewportArguments.maxZoom, m_configuration.minimumScale, maximumViewportArgumentsScaleFactor);
178     applyViewportArgument(m_configuration.initialScale, viewportArgumentsOverridesInitialScale, m_viewportArguments.zoom, m_configuration.minimumScale, m_configuration.maximumScale);
179
180     double minimumViewportArgumentsDimension = 10;
181     double maximumViewportArgumentsDimension = 10000;
182     applyViewportArgument(m_configuration.width, viewportArgumentsOverridesWidth, m_viewportArguments.width, minimumViewportArgumentsDimension, maximumViewportArgumentsDimension);
183     applyViewportArgument(m_configuration.height, viewportArgumentsOverridesHeight, m_viewportArguments.height, minimumViewportArgumentsDimension, maximumViewportArgumentsDimension);
184
185     if (viewportArgumentsOverridesInitialScale || viewportArgumentsOverridesWidth || viewportArgumentsOverridesHeight) {
186         m_configuration.initialScaleIsSet = viewportArgumentsOverridesInitialScale;
187         m_configuration.widthIsSet = viewportArgumentsOverridesWidth;
188         m_configuration.heightIsSet = viewportArgumentsOverridesHeight;
189     }
190
191     if (viewportArgumentValueIsValid(m_viewportArguments.userZoom))
192         m_configuration.allowsUserScaling = m_viewportArguments.userZoom != 0.;
193 }
194
195 int ViewportConfiguration::layoutWidth() const
196 {
197     ASSERT(!constraintsAreAllRelative(m_configuration));
198
199     if (m_configuration.widthIsSet) {
200         // If we scale to fit, then accept the viewport width with sanity checking.
201         if (!m_configuration.initialScaleIsSet) {
202             double maximumScale = this->maximumScale();
203             double maximumContentWidthInViewportCoordinate = maximumScale * m_configuration.width;
204             if (maximumContentWidthInViewportCoordinate < m_minimumLayoutSize.width()) {
205                 // The content zoomed to maxScale does not fit the the view. Return the minimum width
206                 // satisfying the constraint maximumScale.
207                 return std::round(m_minimumLayoutSize.width() / maximumScale);
208             }
209             return std::round(m_configuration.width);
210         }
211
212         // If not, make sure the viewport width and initial scale can co-exist.
213         double initialContentWidthInViewportCoordinate = m_configuration.width * m_configuration.initialScale;
214         if (initialContentWidthInViewportCoordinate < m_contentSize.width()) {
215             // The specified width does not fit in viewport. Return the minimum width that satisfy the initialScale constraint.
216             return std::round(m_minimumLayoutSize.width() / m_configuration.initialScale);
217         }
218         return std::round(m_configuration.width);
219     }
220
221     // If the page has a real scale, then just return the minimum size over the initial scale.
222     if (m_configuration.initialScaleIsSet && !m_configuration.heightIsSet)
223         return std::round(m_minimumLayoutSize.width() / m_configuration.initialScale);
224
225     if (m_minimumLayoutSize.height() > 0)
226         return std::round(m_minimumLayoutSize.width() * layoutHeight() / m_minimumLayoutSize.height());
227     return m_minimumLayoutSize.width();
228 }
229
230 int ViewportConfiguration::layoutHeight() const
231 {
232     ASSERT(!constraintsAreAllRelative(m_configuration));
233
234     if (m_configuration.heightIsSet) {
235         // If we scale to fit, then accept the viewport height with sanity checking.
236         if (!m_configuration.initialScaleIsSet) {
237             double maximumScale = this->maximumScale();
238             double maximumContentHeightInViewportCoordinate = maximumScale * m_configuration.height;
239             if (maximumContentHeightInViewportCoordinate < m_minimumLayoutSize.height()) {
240                 // The content zoomed to maxScale does not fit the the view. Return the minimum height that
241                 // satisfy the constraint maximumScale.
242                 return std::round(m_minimumLayoutSize.height() / maximumScale);
243             }
244             return std::round(m_configuration.height);
245         }
246
247         // If not, make sure the viewport width and initial scale can co-exist.
248         double initialContentHeightInViewportCoordinate = m_configuration.height * m_configuration.initialScale;
249         if (initialContentHeightInViewportCoordinate < m_minimumLayoutSize.height()) {
250             // The specified width does not fit in viewport. Return the minimum height that satisfy the initialScale constraint.
251             return std::round(m_minimumLayoutSize.height() / m_configuration.initialScale);
252         }
253         return std::round(m_configuration.height);
254     }
255
256     // If the page has a real scale, then just return the minimum size over the initial scale.
257     if (m_configuration.initialScaleIsSet && !m_configuration.widthIsSet)
258         return std::round(m_minimumLayoutSize.height() / m_configuration.initialScale);
259
260     if (m_minimumLayoutSize.width() > 0)
261         return std::round(m_minimumLayoutSize.height() * layoutWidth() / m_minimumLayoutSize.width());
262     return m_minimumLayoutSize.height();
263 }
264
265 } // namespace WebCore