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