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