Add a preference to prevent "user-scalable=no" from having any effect
[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(!m_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     bool laidOutWiderThanViewport = m_contentSize.width() > layoutWidth();
107     if (m_viewportArguments.width == ViewportArguments::ValueDeviceWidth)
108         return laidOutWiderThanViewport;
109
110     if (m_configuration.initialScaleIsSet && m_configuration.initialScale == 1)
111         return laidOutWiderThanViewport;
112
113     return false;
114 }
115
116 bool ViewportConfiguration::shouldIgnoreVerticalScalingConstraints() const
117 {
118     if (!m_canIgnoreScalingConstraints)
119         return false;
120
121     bool laidOutTallerThanViewport = m_contentSize.height() > layoutHeight();
122     if (m_viewportArguments.height == ViewportArguments::ValueDeviceHeight)
123         return laidOutTallerThanViewport;
124
125     return false;
126 }
127
128 bool ViewportConfiguration::shouldIgnoreScalingConstraints() const
129 {
130     return shouldIgnoreHorizontalScalingConstraints() || shouldIgnoreVerticalScalingConstraints();
131 }
132
133 double ViewportConfiguration::initialScale() const
134 {
135     ASSERT(!constraintsAreAllRelative(m_configuration));
136
137     // If the document has specified its own initial scale, use it regardless.
138     // This is guaranteed to be sanity checked already, so no need for MIN/MAX.
139     if (m_configuration.initialScaleIsSet && !shouldIgnoreScalingConstraints())
140         return m_configuration.initialScale;
141
142     // If not, it is up to us to determine the initial scale.
143     // We want a scale small enough to fit the document width-wise.
144     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
145     double width = m_contentSize.width() > 0 ? m_contentSize.width() : layoutWidth();
146     double initialScale = 0;
147     if (width > 0 && !shouldIgnoreVerticalScalingConstraints())
148         initialScale = minimumLayoutSize.width() / width;
149
150     // Prevent the initial scale from shrinking to a height smaller than our view's minimum height.
151     double height = m_contentSize.height() > 0 ? m_contentSize.height() : layoutHeight();
152     if (height > 0 && height * initialScale < minimumLayoutSize.height() && !shouldIgnoreHorizontalScalingConstraints())
153         initialScale = minimumLayoutSize.height() / height;
154     return std::min(std::max(initialScale, shouldIgnoreScalingConstraints() ? m_defaultConfiguration.minimumScale : m_configuration.minimumScale), m_configuration.maximumScale);
155 }
156
157 double ViewportConfiguration::minimumScale() const
158 {
159     // If we scale to fit, then this is our minimum scale as well.
160     if (!m_configuration.initialScaleIsSet || shouldIgnoreScalingConstraints())
161         return initialScale();
162
163     // If not, we still need to sanity check our value.
164     double minimumScale = m_configuration.minimumScale;
165
166     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
167     double contentWidth = m_contentSize.width();
168     if (contentWidth > 0 && contentWidth * minimumScale < minimumLayoutSize.width() && !shouldIgnoreVerticalScalingConstraints())
169         minimumScale = minimumLayoutSize.width() / contentWidth;
170
171     double contentHeight = m_contentSize.height();
172     if (contentHeight > 0 && contentHeight * minimumScale < minimumLayoutSize.height() && !shouldIgnoreHorizontalScalingConstraints())
173         minimumScale = minimumLayoutSize.height() / contentHeight;
174
175     minimumScale = std::min(std::max(minimumScale, m_configuration.minimumScale), m_configuration.maximumScale);
176
177     return minimumScale;
178 }
179
180 bool ViewportConfiguration::allowsUserScaling() const
181 {
182     return m_forceAlwaysUserScalable || shouldIgnoreScalingConstraints() || m_configuration.allowsUserScaling;
183 }
184
185 ViewportConfiguration::Parameters ViewportConfiguration::webpageParameters()
186 {
187     Parameters parameters;
188     parameters.width = 980;
189     parameters.widthIsSet = true;
190     parameters.allowsUserScaling = true;
191     parameters.minimumScale = 0.25;
192     parameters.maximumScale = 5;
193     return parameters;
194 }
195
196 ViewportConfiguration::Parameters ViewportConfiguration::textDocumentParameters()
197 {
198     Parameters parameters;
199
200 #if PLATFORM(IOS)
201     parameters.width = static_cast<int>(wkGetScreenSize().width);
202 #else
203     // FIXME: this needs to be unified with ViewportArguments on all ports.
204     parameters.width = 320;
205 #endif
206
207     parameters.widthIsSet = true;
208     parameters.allowsUserScaling = true;
209     parameters.minimumScale = 0.25;
210     parameters.maximumScale = 5;
211     return parameters;
212 }
213
214 ViewportConfiguration::Parameters ViewportConfiguration::imageDocumentParameters()
215 {
216     Parameters parameters;
217     parameters.width = 980;
218     parameters.widthIsSet = true;
219     parameters.allowsUserScaling = true;
220     parameters.minimumScale = 0.01;
221     parameters.maximumScale = 5;
222     return parameters;
223 }
224
225 ViewportConfiguration::Parameters ViewportConfiguration::xhtmlMobileParameters()
226 {
227     Parameters parameters = webpageParameters();
228     parameters.width = 320;
229     return parameters;
230 }
231
232 ViewportConfiguration::Parameters ViewportConfiguration::testingParameters()
233 {
234     Parameters parameters;
235     parameters.initialScale = 1;
236     parameters.initialScaleIsSet = true;
237     parameters.minimumScale = 1;
238     parameters.maximumScale = 5;
239     return parameters;
240 }
241
242 static inline bool viewportArgumentValueIsValid(float value)
243 {
244     return value > 0;
245 }
246
247 template<typename ValueType, typename ViewportArgumentsType>
248 static inline void applyViewportArgument(ValueType& value, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum)
249 {
250     if (viewportArgumentValueIsValid(viewportArgumentValue))
251         value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue)));
252 }
253
254 template<typename ValueType, typename ViewportArgumentsType>
255 static inline void applyViewportArgument(ValueType& value, bool& valueIsSet, ViewportArgumentsType viewportArgumentValue, ValueType minimum, ValueType maximum)
256 {
257     if (viewportArgumentValueIsValid(viewportArgumentValue)) {
258         value = std::min(maximum, std::max(minimum, static_cast<ValueType>(viewportArgumentValue)));
259         valueIsSet = true;
260     } else
261         valueIsSet = false;
262 }
263
264 static inline bool viewportArgumentUserZoomIsSet(float value)
265 {
266     return !value || value == 1;
267 }
268
269 void ViewportConfiguration::updateConfiguration()
270 {
271     m_configuration = m_defaultConfiguration;
272
273     const double minimumViewportArgumentsScaleFactor = 0.1;
274     const double maximumViewportArgumentsScaleFactor = 10.0;
275
276     bool viewportArgumentsOverridesInitialScale;
277     bool viewportArgumentsOverridesWidth;
278     bool viewportArgumentsOverridesHeight;
279
280     applyViewportArgument(m_configuration.minimumScale, m_viewportArguments.minZoom, minimumViewportArgumentsScaleFactor, maximumViewportArgumentsScaleFactor);
281     applyViewportArgument(m_configuration.maximumScale, m_viewportArguments.maxZoom, m_configuration.minimumScale, maximumViewportArgumentsScaleFactor);
282     applyViewportArgument(m_configuration.initialScale, viewportArgumentsOverridesInitialScale, m_viewportArguments.zoom, m_configuration.minimumScale, m_configuration.maximumScale);
283
284     double minimumViewportArgumentsDimension = 10;
285     double maximumViewportArgumentsDimension = 10000;
286     applyViewportArgument(m_configuration.width, viewportArgumentsOverridesWidth, viewportArgumentsLength(m_viewportArguments.width), minimumViewportArgumentsDimension, maximumViewportArgumentsDimension);
287     applyViewportArgument(m_configuration.height, viewportArgumentsOverridesHeight, viewportArgumentsLength(m_viewportArguments.height), minimumViewportArgumentsDimension, maximumViewportArgumentsDimension);
288
289     if (viewportArgumentsOverridesInitialScale || viewportArgumentsOverridesWidth || viewportArgumentsOverridesHeight) {
290         m_configuration.initialScaleIsSet = viewportArgumentsOverridesInitialScale;
291         m_configuration.widthIsSet = viewportArgumentsOverridesWidth;
292         m_configuration.heightIsSet = viewportArgumentsOverridesHeight;
293     }
294
295     if (viewportArgumentUserZoomIsSet(m_viewportArguments.userZoom))
296         m_configuration.allowsUserScaling = m_viewportArguments.userZoom != 0.;
297 }
298
299 double ViewportConfiguration::viewportArgumentsLength(double length) const
300 {
301     if (length == ViewportArguments::ValueDeviceWidth)
302         return m_minimumLayoutSize.width();
303     if (length == ViewportArguments::ValueDeviceHeight)
304         return m_minimumLayoutSize.height();
305     return length;
306 }
307
308 int ViewportConfiguration::layoutWidth() const
309 {
310     ASSERT(!constraintsAreAllRelative(m_configuration));
311
312     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
313     if (m_configuration.widthIsSet) {
314         // If we scale to fit, then accept the viewport width with sanity checking.
315         if (!m_configuration.initialScaleIsSet) {
316             double maximumScale = this->maximumScale();
317             double maximumContentWidthInViewportCoordinate = maximumScale * m_configuration.width;
318             if (maximumContentWidthInViewportCoordinate < minimumLayoutSize.width()) {
319                 // The content zoomed to maxScale does not fit the the view. Return the minimum width
320                 // satisfying the constraint maximumScale.
321                 return std::round(minimumLayoutSize.width() / maximumScale);
322             }
323             return std::round(m_configuration.width);
324         }
325
326         // If not, make sure the viewport width and initial scale can co-exist.
327         double initialContentWidthInViewportCoordinate = m_configuration.width * m_configuration.initialScale;
328         if (initialContentWidthInViewportCoordinate < minimumLayoutSize.width()) {
329             // The specified width does not fit in viewport. Return the minimum width that satisfy the initialScale constraint.
330             return std::round(minimumLayoutSize.width() / m_configuration.initialScale);
331         }
332         return std::round(m_configuration.width);
333     }
334
335     // If the page has a real scale, then just return the minimum size over the initial scale.
336     if (m_configuration.initialScaleIsSet && !m_configuration.heightIsSet)
337         return std::round(minimumLayoutSize.width() / m_configuration.initialScale);
338
339     if (minimumLayoutSize.height() > 0)
340         return std::round(minimumLayoutSize.width() * layoutHeight() / minimumLayoutSize.height());
341     return minimumLayoutSize.width();
342 }
343
344 int ViewportConfiguration::layoutHeight() const
345 {
346     ASSERT(!constraintsAreAllRelative(m_configuration));
347
348     const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
349     if (m_configuration.heightIsSet) {
350         // If we scale to fit, then accept the viewport height with sanity checking.
351         if (!m_configuration.initialScaleIsSet) {
352             double maximumScale = this->maximumScale();
353             double maximumContentHeightInViewportCoordinate = maximumScale * m_configuration.height;
354             if (maximumContentHeightInViewportCoordinate < minimumLayoutSize.height()) {
355                 // The content zoomed to maxScale does not fit the the view. Return the minimum height that
356                 // satisfy the constraint maximumScale.
357                 return std::round(minimumLayoutSize.height() / maximumScale);
358             }
359             return std::round(m_configuration.height);
360         }
361
362         // If not, make sure the viewport width and initial scale can co-exist.
363         double initialContentHeightInViewportCoordinate = m_configuration.height * m_configuration.initialScale;
364         if (initialContentHeightInViewportCoordinate < minimumLayoutSize.height()) {
365             // The specified width does not fit in viewport. Return the minimum height that satisfy the initialScale constraint.
366             return std::round(minimumLayoutSize.height() / m_configuration.initialScale);
367         }
368         return std::round(m_configuration.height);
369     }
370
371     // If the page has a real scale, then just return the minimum size over the initial scale.
372     if (m_configuration.initialScaleIsSet && !m_configuration.widthIsSet)
373         return std::round(minimumLayoutSize.height() / m_configuration.initialScale);
374
375     if (minimumLayoutSize.width() > 0)
376         return std::round(minimumLayoutSize.height() * layoutWidth() / minimumLayoutSize.width());
377     return minimumLayoutSize.height();
378 }
379
380 #ifndef NDEBUG
381 class ViewportConfigurationTextStream : public TextStream {
382 public:
383     ViewportConfigurationTextStream()
384         : m_indent(0)
385     {
386     }
387
388     using TextStream::operator<<;
389
390     ViewportConfigurationTextStream& operator<<(const ViewportConfiguration::Parameters&);
391     ViewportConfigurationTextStream& operator<<(const ViewportArguments&);
392
393     void increaseIndent() { ++m_indent; }
394     void decreaseIndent() { --m_indent; ASSERT(m_indent >= 0); }
395
396     void writeIndent();
397
398 private:
399     int m_indent;
400 };
401
402 template <typename T>
403 static void dumpProperty(ViewportConfigurationTextStream& ts, String name, T value)
404 {
405     ts << "\n";
406     ts.increaseIndent();
407     ts.writeIndent();
408     ts << "(" << name << " ";
409     ts << value << ")";
410     ts.decreaseIndent();
411 }
412
413 void ViewportConfigurationTextStream::writeIndent()
414 {
415     for (int i = 0; i < m_indent; ++i)
416         *this << "  ";
417 }
418
419 ViewportConfigurationTextStream& ViewportConfigurationTextStream::operator<<(const ViewportConfiguration::Parameters& parameters)
420 {
421     ViewportConfigurationTextStream& ts = *this;
422
423     ts.increaseIndent();
424     ts << "\n";
425     ts.writeIndent();
426     ts << "(width " << parameters.width << ", set: " << (parameters.widthIsSet ? "true" : "false") << ")";
427
428     ts << "\n";
429     ts.writeIndent();
430     ts << "(height " << parameters.height << ", set: " << (parameters.heightIsSet ? "true" : "false") << ")";
431
432     ts << "\n";
433     ts.writeIndent();
434     ts << "(initialScale " << parameters.width << ", set: " << (parameters.initialScaleIsSet ? "true" : "false") << ")";
435     ts.decreaseIndent();
436
437     dumpProperty(ts, "minimumScale", parameters.minimumScale);
438     dumpProperty(ts, "maximumScale", parameters.maximumScale);
439     dumpProperty(ts, "allowsUserScaling", parameters.allowsUserScaling);
440
441     return ts;
442 }
443
444 ViewportConfigurationTextStream& ViewportConfigurationTextStream::operator<<(const ViewportArguments& viewportArguments)
445 {
446     ViewportConfigurationTextStream& ts = *this;
447
448     ts.increaseIndent();
449
450     ts << "\n";
451     ts.writeIndent();
452     ts << "(width " << viewportArguments.width << ", minWidth " << viewportArguments.minWidth << ", maxWidth " << viewportArguments.maxWidth << ")";
453
454     ts << "\n";
455     ts.writeIndent();
456     ts << "(height " << viewportArguments.height << ", minHeight " << viewportArguments.minHeight << ", maxHeight " << viewportArguments.maxHeight << ")";
457
458     ts << "\n";
459     ts.writeIndent();
460     ts << "(zoom " << viewportArguments.zoom << ", minZoom " << viewportArguments.minZoom << ", maxZoom " << viewportArguments.maxZoom << ")";
461     ts.decreaseIndent();
462
463     return ts;
464 }
465
466 CString ViewportConfiguration::description() const
467 {
468     ViewportConfigurationTextStream ts;
469
470     ts << "(viewport-configuration " << (void*)this;
471     ts << "\n";
472     ts.increaseIndent();
473     ts.writeIndent();
474     ts << "(viewport arguments";
475     ts << m_viewportArguments;
476     ts << ")";
477     ts.decreaseIndent();
478
479     ts << "\n";
480     ts.increaseIndent();
481     ts.writeIndent();
482     ts << "(configuration";
483     ts << m_configuration;
484     ts << ")";
485     ts.decreaseIndent();
486
487     ts << "\n";
488     ts.increaseIndent();
489     ts.writeIndent();
490     ts << "(default configuration";
491     ts << m_defaultConfiguration;
492     ts << ")";
493     ts.decreaseIndent();
494
495     dumpProperty(ts, "contentSize", m_contentSize);
496     dumpProperty(ts, "minimumLayoutSize", m_minimumLayoutSize);
497
498     ts << "\n";
499     ts.increaseIndent();
500     ts.writeIndent();
501     ts << "(computed initial scale " << initialScale() << ")\n";
502     ts.writeIndent();
503     ts << "(computed minimum scale " << minimumScale() << ")\n";
504     ts.writeIndent();
505     ts << "(computed layout size " << layoutSize() << ")\n";
506     ts.writeIndent();
507     ts << "(ignoring horizontal scaling constraints " << (shouldIgnoreHorizontalScalingConstraints() ? "true" : "false") << ")\n";
508     ts.writeIndent();
509     ts << "(ignoring vertical scaling constraints " << (shouldIgnoreVerticalScalingConstraints() ? "true" : "false") << ")";
510     ts.decreaseIndent();
511
512     ts << ")\n";
513
514     return ts.release().utf8();
515 }
516
517 void ViewportConfiguration::dump() const
518 {
519     WTFLogAlways("%s", description().data());
520 }
521
522 #endif
523
524 } // namespace WebCore