Unreviewed, rolling out r191902.
[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 <WebCore/TextStream.h>
30 #include <wtf/Assertions.h>
31 #include <wtf/MathExtras.h>
32 #include <wtf/text/CString.h>
33
34 #if PLATFORM(IOS)
35 #include "WebCoreSystemInterface.h"
36 #endif
37
38 namespace WebCore {
39
40 #if !ASSERT_DISABLED
41 static bool constraintsAreAllRelative(const ViewportConfiguration::Parameters& configuration)
42 {
43     return !configuration.widthIsSet && !configuration.heightIsSet && !configuration.initialScaleIsSet;
44 }
45 #endif
46
47 ViewportConfiguration::ViewportConfiguration()
48     : m_minimumLayoutSize(1024, 768)
49     , m_canIgnoreScalingConstraints(false)
50     , m_forceAlwaysUserScalable(false)
51 {
52     // Setup a reasonable default configuration to avoid computing infinite scale/sizes.
53     // Those are the original iPhone configuration.
54     m_defaultConfiguration = ViewportConfiguration::webpageParameters();
55     updateConfiguration();
56 }
57
58 void ViewportConfiguration::setDefaultConfiguration(const ViewportConfiguration::Parameters& defaultConfiguration)
59 {
60     ASSERT(!constraintsAreAllRelative(m_configuration));
61     ASSERT(!defaultConfiguration.initialScaleIsSet || defaultConfiguration.initialScale > 0);
62     ASSERT(defaultConfiguration.minimumScale > 0);
63     ASSERT(defaultConfiguration.maximumScale >= defaultConfiguration.minimumScale);
64
65     m_defaultConfiguration = defaultConfiguration;
66     updateConfiguration();
67 }
68
69 void ViewportConfiguration::setContentsSize(const IntSize& contentSize)
70 {
71     if (m_contentSize == contentSize)
72         return;
73
74     m_contentSize = contentSize;
75     updateConfiguration();
76 }
77
78 void ViewportConfiguration::setMinimumLayoutSize(const FloatSize& minimumLayoutSize)
79 {
80     if (m_minimumLayoutSize == minimumLayoutSize)
81         return;
82
83     m_minimumLayoutSize = minimumLayoutSize;
84     updateConfiguration();
85 }
86
87 void ViewportConfiguration::setViewportArguments(const ViewportArguments& viewportArguments)
88 {
89     if (m_viewportArguments == viewportArguments)
90         return;
91
92     m_viewportArguments = viewportArguments;
93     updateConfiguration();
94 }
95
96 IntSize ViewportConfiguration::layoutSize() const
97 {
98     return IntSize(layoutWidth(), layoutHeight());
99 }
100
101 bool ViewportConfiguration::shouldIgnoreHorizontalScalingConstraints() const
102 {
103     if (!m_canIgnoreScalingConstraints)
104         return false;
105
106     if (!m_configuration.allowsShrinkToFit)
107         return false;
108
109     bool laidOutWiderThanViewport = m_contentSize.width() > layoutWidth();
110     if (m_viewportArguments.width == ViewportArguments::ValueDeviceWidth)
111         return laidOutWiderThanViewport;
112
113     if (m_configuration.initialScaleIsSet && m_configuration.initialScale == 1)
114         return laidOutWiderThanViewport;
115
116     return false;
117 }
118
119 bool ViewportConfiguration::shouldIgnoreVerticalScalingConstraints() const
120 {
121     if (!m_canIgnoreScalingConstraints)
122         return false;
123
124     if (!m_configuration.allowsShrinkToFit)
125         return false;
126
127     bool laidOutTallerThanViewport = m_contentSize.height() > layoutHeight();
128     if (m_viewportArguments.height == ViewportArguments::ValueDeviceHeight && m_viewportArguments.width == ViewportArguments::ValueAuto)
129         return laidOutTallerThanViewport;
130
131     return false;
132 }
133
134 bool ViewportConfiguration::shouldIgnoreScalingConstraints() const
135 {
136     return shouldIgnoreHorizontalScalingConstraints() || shouldIgnoreVerticalScalingConstraints();
137 }
138
139 double ViewportConfiguration::initialScaleFromSize(double width, double height, bool shouldIgnoreScalingConstraints) const
140 {
141     ASSERT(!constraintsAreAllRelative(m_configuration));
142
143     // If the document has specified its own initial scale, use it regardless.
144     // This is guaranteed to be sanity checked already, so no need for MIN/MAX.
145     if (m_configuration.initialScaleIsSet && !shouldIgnoreScalingConstraints)
146         return m_configuration.initialScale;
147
148     // If not, it is up to us to determine the initial scale.
149     // We want a scale small enough to fit the document width-wise.
150     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
151     double initialScale = 0;
152     if (width > 0 && !shouldIgnoreVerticalScalingConstraints())
153         initialScale = minimumLayoutSize.width() / width;
154
155     // Prevent the initial scale from shrinking to a height smaller than our view's minimum height.
156     if (height > 0 && height * initialScale < minimumLayoutSize.height() && !shouldIgnoreHorizontalScalingConstraints())
157         initialScale = minimumLayoutSize.height() / height;
158     return std::min(std::max(initialScale, shouldIgnoreScalingConstraints ? m_defaultConfiguration.minimumScale : m_configuration.minimumScale), m_configuration.maximumScale);
159 }
160
161 double ViewportConfiguration::initialScale() const
162 {
163     return initialScaleFromSize(m_contentSize.width() > 0 ? m_contentSize.width() : layoutWidth(), m_contentSize.height() > 0 ? m_contentSize.height() : layoutHeight(), shouldIgnoreScalingConstraints());
164 }
165
166 double ViewportConfiguration::initialScaleIgnoringContentSize() const
167 {
168     return initialScaleFromSize(layoutWidth(), layoutHeight(), false);
169 }
170
171 double ViewportConfiguration::minimumScale() const
172 {
173     // If we scale to fit, then this is our minimum scale as well.
174     if (!m_configuration.initialScaleIsSet || shouldIgnoreScalingConstraints())
175         return initialScale();
176
177     // If not, we still need to sanity check our value.
178     double minimumScale = m_configuration.minimumScale;
179
180     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
181     double contentWidth = m_contentSize.width();
182     if (contentWidth > 0 && contentWidth * minimumScale < minimumLayoutSize.width() && !shouldIgnoreVerticalScalingConstraints())
183         minimumScale = minimumLayoutSize.width() / contentWidth;
184
185     double contentHeight = m_contentSize.height();
186     if (contentHeight > 0 && contentHeight * minimumScale < minimumLayoutSize.height() && !shouldIgnoreHorizontalScalingConstraints())
187         minimumScale = minimumLayoutSize.height() / contentHeight;
188
189     minimumScale = std::min(std::max(minimumScale, m_configuration.minimumScale), m_configuration.maximumScale);
190
191     return minimumScale;
192 }
193
194 bool ViewportConfiguration::allowsUserScaling() const
195 {
196     return m_forceAlwaysUserScalable || shouldIgnoreScalingConstraints() || m_configuration.allowsUserScaling;
197 }
198
199 ViewportConfiguration::Parameters ViewportConfiguration::webpageParameters()
200 {
201     Parameters parameters;
202     parameters.width = 980;
203     parameters.widthIsSet = true;
204     parameters.allowsUserScaling = true;
205     parameters.allowsShrinkToFit = true;
206     parameters.minimumScale = 0.25;
207     parameters.maximumScale = 5;
208     return parameters;
209 }
210
211 ViewportConfiguration::Parameters ViewportConfiguration::textDocumentParameters()
212 {
213     Parameters parameters;
214
215 #if PLATFORM(IOS)
216     parameters.width = static_cast<int>(wkGetScreenSize().width);
217 #else
218     // FIXME: this needs to be unified with ViewportArguments on all ports.
219     parameters.width = 320;
220 #endif
221
222     parameters.widthIsSet = true;
223     parameters.allowsUserScaling = true;
224     parameters.allowsShrinkToFit = false;
225     parameters.minimumScale = 0.25;
226     parameters.maximumScale = 5;
227     return parameters;
228 }
229
230 ViewportConfiguration::Parameters ViewportConfiguration::imageDocumentParameters()
231 {
232     Parameters parameters;
233     parameters.width = 980;
234     parameters.widthIsSet = true;
235     parameters.allowsUserScaling = true;
236     parameters.allowsShrinkToFit = false;
237     parameters.minimumScale = 0.01;
238     parameters.maximumScale = 5;
239     return parameters;
240 }
241
242 ViewportConfiguration::Parameters ViewportConfiguration::xhtmlMobileParameters()
243 {
244     Parameters parameters = webpageParameters();
245     parameters.width = 320;
246     return parameters;
247 }
248
249 ViewportConfiguration::Parameters ViewportConfiguration::testingParameters()
250 {
251     Parameters parameters;
252     parameters.initialScale = 1;
253     parameters.initialScaleIsSet = true;
254     parameters.allowsShrinkToFit = true;
255     parameters.minimumScale = 1;
256     parameters.maximumScale = 5;
257     return parameters;
258 }
259
260 static inline bool viewportArgumentValueIsValid(float value)
261 {
262     return value > 0;
263 }
264
265 template<typename ValueType, typename ViewportArgumentsType>
266 static inline void applyViewportArgument(ValueType& value, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum)
267 {
268     if (viewportArgumentValueIsValid(viewportArgumentValue))
269         value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue)));
270 }
271
272 template<typename ValueType, typename ViewportArgumentsType>
273 static inline void applyViewportArgument(ValueType& value, bool& valueIsSet, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum)
274 {
275     if (viewportArgumentValueIsValid(viewportArgumentValue)) {
276         value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue)));
277         valueIsSet = true;
278     } else
279         valueIsSet = false;
280 }
281
282 static inline bool booleanViewportArgumentIsSet(float value)
283 {
284     return !value || value == 1;
285 }
286
287 void ViewportConfiguration::updateConfiguration()
288 {
289     m_configuration = m_defaultConfiguration;
290
291     const double minimumViewportArgumentsScaleFactor = 0.1;
292     const double maximumViewportArgumentsScaleFactor = 10.0;
293
294     bool viewportArgumentsOverridesInitialScale;
295     bool viewportArgumentsOverridesWidth;
296     bool viewportArgumentsOverridesHeight;
297
298     applyViewportArgument(m_configuration.minimumScale, m_viewportArguments.minZoom, minimumViewportArgumentsScaleFactor, maximumViewportArgumentsScaleFactor);
299     applyViewportArgument(m_configuration.maximumScale, m_viewportArguments.maxZoom, m_configuration.minimumScale, maximumViewportArgumentsScaleFactor);
300     applyViewportArgument(m_configuration.initialScale, viewportArgumentsOverridesInitialScale, m_viewportArguments.zoom, m_configuration.minimumScale, m_configuration.maximumScale);
301
302     double minimumViewportArgumentsDimension = 10;
303     double maximumViewportArgumentsDimension = 10000;
304     applyViewportArgument(m_configuration.width, viewportArgumentsOverridesWidth, viewportArgumentsLength(m_viewportArguments.width), minimumViewportArgumentsDimension, maximumViewportArgumentsDimension);
305     applyViewportArgument(m_configuration.height, viewportArgumentsOverridesHeight, viewportArgumentsLength(m_viewportArguments.height), minimumViewportArgumentsDimension, maximumViewportArgumentsDimension);
306
307     if (viewportArgumentsOverridesInitialScale || viewportArgumentsOverridesWidth || viewportArgumentsOverridesHeight) {
308         m_configuration.initialScaleIsSet = viewportArgumentsOverridesInitialScale;
309         m_configuration.widthIsSet = viewportArgumentsOverridesWidth;
310         m_configuration.heightIsSet = viewportArgumentsOverridesHeight;
311     }
312
313     if (booleanViewportArgumentIsSet(m_viewportArguments.userZoom))
314         m_configuration.allowsUserScaling = m_viewportArguments.userZoom != 0.;
315
316     if (booleanViewportArgumentIsSet(m_viewportArguments.shrinkToFit))
317         m_configuration.allowsShrinkToFit = m_viewportArguments.shrinkToFit != 0.;
318 }
319
320 double ViewportConfiguration::viewportArgumentsLength(double length) const
321 {
322     if (length == ViewportArguments::ValueDeviceWidth)
323         return m_minimumLayoutSize.width();
324     if (length == ViewportArguments::ValueDeviceHeight)
325         return m_minimumLayoutSize.height();
326     return length;
327 }
328
329 int ViewportConfiguration::layoutWidth() const
330 {
331     ASSERT(!constraintsAreAllRelative(m_configuration));
332
333     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
334     if (m_configuration.widthIsSet) {
335         // If we scale to fit, then accept the viewport width with sanity checking.
336         if (!m_configuration.initialScaleIsSet) {
337             double maximumScale = this->maximumScale();
338             double maximumContentWidthInViewportCoordinate = maximumScale * m_configuration.width;
339             if (maximumContentWidthInViewportCoordinate < minimumLayoutSize.width()) {
340                 // The content zoomed to maxScale does not fit the the view. Return the minimum width
341                 // satisfying the constraint maximumScale.
342                 return std::round(minimumLayoutSize.width() / maximumScale);
343             }
344             return std::round(m_configuration.width);
345         }
346
347         // If not, make sure the viewport width and initial scale can co-exist.
348         double initialContentWidthInViewportCoordinate = m_configuration.width * m_configuration.initialScale;
349         if (initialContentWidthInViewportCoordinate < minimumLayoutSize.width()) {
350             // The specified width does not fit in viewport. Return the minimum width that satisfy the initialScale constraint.
351             return std::round(minimumLayoutSize.width() / m_configuration.initialScale);
352         }
353         return std::round(m_configuration.width);
354     }
355
356     // If the page has a real scale, then just return the minimum size over the initial scale.
357     if (m_configuration.initialScaleIsSet && !m_configuration.heightIsSet)
358         return std::round(minimumLayoutSize.width() / m_configuration.initialScale);
359
360     if (minimumLayoutSize.height() > 0)
361         return std::round(minimumLayoutSize.width() * layoutHeight() / minimumLayoutSize.height());
362     return minimumLayoutSize.width();
363 }
364
365 int ViewportConfiguration::layoutHeight() const
366 {
367     ASSERT(!constraintsAreAllRelative(m_configuration));
368
369     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
370     if (m_configuration.heightIsSet) {
371         // If we scale to fit, then accept the viewport height with sanity checking.
372         if (!m_configuration.initialScaleIsSet) {
373             double maximumScale = this->maximumScale();
374             double maximumContentHeightInViewportCoordinate = maximumScale * m_configuration.height;
375             if (maximumContentHeightInViewportCoordinate < minimumLayoutSize.height()) {
376                 // The content zoomed to maxScale does not fit the the view. Return the minimum height that
377                 // satisfy the constraint maximumScale.
378                 return std::round(minimumLayoutSize.height() / maximumScale);
379             }
380             return std::round(m_configuration.height);
381         }
382
383         // If not, make sure the viewport width and initial scale can co-exist.
384         double initialContentHeightInViewportCoordinate = m_configuration.height * m_configuration.initialScale;
385         if (initialContentHeightInViewportCoordinate < minimumLayoutSize.height()) {
386             // The specified width does not fit in viewport. Return the minimum height that satisfy the initialScale constraint.
387             return std::round(minimumLayoutSize.height() / m_configuration.initialScale);
388         }
389         return std::round(m_configuration.height);
390     }
391
392     // If the page has a real scale, then just return the minimum size over the initial scale.
393     if (m_configuration.initialScaleIsSet && !m_configuration.widthIsSet)
394         return std::round(minimumLayoutSize.height() / m_configuration.initialScale);
395
396     if (minimumLayoutSize.width() > 0)
397         return std::round(minimumLayoutSize.height() * layoutWidth() / minimumLayoutSize.width());
398     return minimumLayoutSize.height();
399 }
400
401 #ifndef NDEBUG
402
403 TextStream& operator<<(TextStream& ts, const ViewportConfiguration::Parameters& parameters)
404 {
405     ts.increaseIndent();
406     ts << "\n";
407     ts.writeIndent();
408     ts << "(width " << parameters.width << ", set: " << (parameters.widthIsSet ? "true" : "false") << ")";
409
410     ts << "\n";
411     ts.writeIndent();
412     ts << "(height " << parameters.height << ", set: " << (parameters.heightIsSet ? "true" : "false") << ")";
413
414     ts << "\n";
415     ts.writeIndent();
416     ts << "(initialScale " << parameters.initialScale << ", set: " << (parameters.initialScaleIsSet ? "true" : "false") << ")";
417     ts.decreaseIndent();
418
419     ts.dumpProperty("minimumScale", parameters.minimumScale);
420     ts.dumpProperty("maximumScale", parameters.maximumScale);
421     ts.dumpProperty("allowsUserScaling", parameters.allowsUserScaling);
422     ts.dumpProperty("allowsShrinkToFit", parameters.allowsShrinkToFit);
423
424     return ts;
425 }
426
427 CString ViewportConfiguration::description() const
428 {
429     TextStream ts;
430
431     ts << "(viewport-configuration " << (void*)this;
432     ts << "\n";
433     ts.increaseIndent();
434     ts.writeIndent();
435     ts << "(viewport arguments";
436     ts << m_viewportArguments;
437     ts << ")";
438     ts.decreaseIndent();
439
440     ts << "\n";
441     ts.increaseIndent();
442     ts.writeIndent();
443     ts << "(configuration";
444     ts << m_configuration;
445     ts << ")";
446     ts.decreaseIndent();
447
448     ts << "\n";
449     ts.increaseIndent();
450     ts.writeIndent();
451     ts << "(default configuration";
452     ts << m_defaultConfiguration;
453     ts << ")";
454     ts.decreaseIndent();
455
456     ts.dumpProperty("contentSize", m_contentSize);
457     ts.dumpProperty("minimumLayoutSize", m_minimumLayoutSize);
458
459     ts << "\n";
460     ts.increaseIndent();
461     ts.writeIndent();
462     ts << "(computed initial scale " << initialScale() << ")\n";
463     ts.writeIndent();
464     ts << "(computed minimum scale " << minimumScale() << ")\n";
465     ts.writeIndent();
466     ts << "(computed layout size " << layoutSize() << ")\n";
467     ts.writeIndent();
468     ts << "(ignoring horizontal scaling constraints " << (shouldIgnoreHorizontalScalingConstraints() ? "true" : "false") << ")\n";
469     ts.writeIndent();
470     ts << "(ignoring vertical scaling constraints " << (shouldIgnoreVerticalScalingConstraints() ? "true" : "false") << ")";
471     ts.decreaseIndent();
472
473     ts << ")\n";
474
475     return ts.release().utf8();
476 }
477
478 void ViewportConfiguration::dump() const
479 {
480     WTFLogAlways("%s", description().data());
481 }
482
483 #endif
484
485 } // namespace WebCore