Provide a viewport parameter to disable clipping to the safe area
[WebKit-https.git] / Source / WebCore / dom / ViewportArguments.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  *           (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
7  * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
9  * Copyright (C) 2012 Intel Corporation. All rights reserved.
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public License
22  * along with this library; see the file COPYING.LIB.  If not, write to
23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  *
26  */
27
28 #include "config.h"
29 #include "ViewportArguments.h"
30
31 #include "Document.h"
32 #include "Frame.h"
33 #include "IntSize.h"
34 #include "ScriptableDocumentParser.h"
35 #include "TextStream.h"
36
37 namespace WebCore {
38
39 #if PLATFORM(GTK)
40 const float ViewportArguments::deprecatedTargetDPI = 160;
41 #endif
42
43 static const float& compareIgnoringAuto(const float& value1, const float& value2, const float& (*compare) (const float&, const float&))
44 {
45     ASSERT(value1 != ViewportArguments::ValueAuto || value2 != ViewportArguments::ValueAuto);
46
47     if (value1 == ViewportArguments::ValueAuto)
48         return value2;
49
50     if (value2 == ViewportArguments::ValueAuto)
51         return value1;
52
53     return compare(value1, value2);
54 }
55
56 static inline float clampLengthValue(float value)
57 {
58     ASSERT(value != ViewportArguments::ValueDeviceWidth);
59     ASSERT(value != ViewportArguments::ValueDeviceHeight);
60
61     // Limits as defined in the css-device-adapt spec.
62     if (value != ViewportArguments::ValueAuto)
63         return std::min<float>(10000, std::max<float>(value, 1));
64     return value;
65 }
66
67 static inline float clampScaleValue(float value)
68 {
69     ASSERT(value != ViewportArguments::ValueDeviceWidth);
70     ASSERT(value != ViewportArguments::ValueDeviceHeight);
71
72     // Limits as defined in the css-device-adapt spec.
73     if (value != ViewportArguments::ValueAuto)
74         return std::min<float>(10, std::max<float>(value, 0.1));
75     return value;
76 }
77
78 ViewportAttributes ViewportArguments::resolve(const FloatSize& initialViewportSize, const FloatSize& deviceSize, int defaultWidth) const
79 {
80     float resultWidth = width;
81     float resultMaxWidth = maxWidth;
82     float resultMinWidth = minWidth;
83     float resultHeight = height;
84     float resultMinHeight = minHeight;
85     float resultMaxHeight = maxHeight;
86     float resultZoom = zoom;
87     float resultMinZoom = minZoom;
88     float resultMaxZoom = maxZoom;
89
90     switch (int(resultWidth)) {
91     case ViewportArguments::ValueDeviceWidth:
92         resultWidth = deviceSize.width();
93         break;
94     case ViewportArguments::ValueDeviceHeight:
95         resultWidth = deviceSize.height();
96         break;
97     }
98
99     switch (int(resultHeight)) {
100     case ViewportArguments::ValueDeviceWidth:
101         resultHeight = deviceSize.width();
102         break;
103     case ViewportArguments::ValueDeviceHeight:
104         resultHeight = deviceSize.height();
105         break;
106     }
107
108     if (type == ViewportArguments::CSSDeviceAdaptation) {
109         switch (int(resultMinWidth)) {
110         case ViewportArguments::ValueDeviceWidth:
111             resultMinWidth = deviceSize.width();
112             break;
113         case ViewportArguments::ValueDeviceHeight:
114             resultMinWidth = deviceSize.height();
115             break;
116         }
117
118         switch (int(resultMaxWidth)) {
119         case ViewportArguments::ValueDeviceWidth:
120             resultMaxWidth = deviceSize.width();
121             break;
122         case ViewportArguments::ValueDeviceHeight:
123             resultMaxWidth = deviceSize.height();
124             break;
125         }
126
127         switch (int(resultMinHeight)) {
128         case ViewportArguments::ValueDeviceWidth:
129             resultMinHeight = deviceSize.width();
130             break;
131         case ViewportArguments::ValueDeviceHeight:
132             resultMinHeight = deviceSize.height();
133             break;
134         }
135
136         switch (int(resultMaxHeight)) {
137         case ViewportArguments::ValueDeviceWidth:
138             resultMaxHeight = deviceSize.width();
139             break;
140         case ViewportArguments::ValueDeviceHeight:
141             resultMaxHeight = deviceSize.height();
142             break;
143         }
144
145         if (resultMinWidth != ViewportArguments::ValueAuto || resultMaxWidth != ViewportArguments::ValueAuto)
146             resultWidth = compareIgnoringAuto(resultMinWidth, compareIgnoringAuto(resultMaxWidth, deviceSize.width(), std::min), std::max);
147
148         if (resultMinHeight != ViewportArguments::ValueAuto || resultMaxHeight != ViewportArguments::ValueAuto)
149             resultHeight = compareIgnoringAuto(resultMinHeight, compareIgnoringAuto(resultMaxHeight, deviceSize.height(), std::min), std::max);
150
151         if (resultMinZoom != ViewportArguments::ValueAuto && resultMaxZoom != ViewportArguments::ValueAuto)
152             resultMaxZoom = std::max(resultMinZoom, resultMaxZoom);
153
154         if (resultZoom != ViewportArguments::ValueAuto)
155             resultZoom = compareIgnoringAuto(resultMinZoom, compareIgnoringAuto(resultMaxZoom, resultZoom, std::min), std::max);
156
157         if (resultWidth == ViewportArguments::ValueAuto && resultZoom == ViewportArguments::ValueAuto)
158             resultWidth = deviceSize.width();
159
160         if (resultWidth == ViewportArguments::ValueAuto && resultHeight == ViewportArguments::ValueAuto)
161             resultWidth = deviceSize.width() / resultZoom;
162
163         if (resultWidth == ViewportArguments::ValueAuto)
164             resultWidth = resultHeight * deviceSize.width() / deviceSize.height();
165
166         if (resultHeight == ViewportArguments::ValueAuto)
167             resultHeight = resultWidth * deviceSize.height() / deviceSize.width();
168
169         if (resultZoom != ViewportArguments::ValueAuto || resultMaxZoom != ViewportArguments::ValueAuto) {
170             resultWidth = compareIgnoringAuto(resultWidth, deviceSize.width() / compareIgnoringAuto(resultZoom, resultMaxZoom, std::min), std::max);
171             resultHeight = compareIgnoringAuto(resultHeight, deviceSize.height() / compareIgnoringAuto(resultZoom, resultMaxZoom, std::min), std::max);
172         }
173
174         resultWidth = std::max<float>(1, resultWidth);
175         resultHeight = std::max<float>(1, resultHeight);
176     }
177
178     if (type != ViewportArguments::CSSDeviceAdaptation && type != ViewportArguments::Implicit) {
179         // Clamp values to a valid range, but not for @viewport since is
180         // not mandated by the specification.
181         resultWidth = clampLengthValue(resultWidth);
182         resultHeight = clampLengthValue(resultHeight);
183         resultZoom = clampScaleValue(resultZoom);
184         resultMinZoom = clampScaleValue(resultMinZoom);
185         resultMaxZoom = clampScaleValue(resultMaxZoom);
186     }
187
188     ViewportAttributes result;
189
190     // Resolve minimum-scale and maximum-scale values according to spec.
191     if (resultMinZoom == ViewportArguments::ValueAuto)
192         result.minimumScale = float(0.25);
193     else
194         result.minimumScale = resultMinZoom;
195
196     if (resultMaxZoom == ViewportArguments::ValueAuto) {
197         result.maximumScale = 5;
198         result.minimumScale = std::min<float>(5, result.minimumScale);
199     } else
200         result.maximumScale = resultMaxZoom;
201     result.maximumScale = std::max(result.minimumScale, result.maximumScale);
202
203     // Resolve initial-scale value.
204     result.initialScale = resultZoom;
205     if (resultZoom == ViewportArguments::ValueAuto) {
206         result.initialScale = initialViewportSize.width() / defaultWidth;
207         if (resultWidth != ViewportArguments::ValueAuto)
208             result.initialScale = initialViewportSize.width() / resultWidth;
209         if (resultHeight != ViewportArguments::ValueAuto) {
210             // if 'auto', the initial-scale will be negative here and thus ignored.
211             result.initialScale = std::max<float>(result.initialScale, initialViewportSize.height() / resultHeight);
212         }
213     }
214
215     // Constrain initial-scale value to minimum-scale/maximum-scale range.
216     result.initialScale = std::min(result.maximumScale, std::max(result.minimumScale, result.initialScale));
217
218     // Resolve width value.
219     if (resultWidth == ViewportArguments::ValueAuto) {
220         if (resultZoom == ViewportArguments::ValueAuto)
221             resultWidth = defaultWidth;
222         else if (resultHeight != ViewportArguments::ValueAuto)
223             resultWidth = resultHeight * (initialViewportSize.width() / initialViewportSize.height());
224         else
225             resultWidth = initialViewportSize.width() / result.initialScale;
226     }
227
228     // Resolve height value.
229     if (resultHeight == ViewportArguments::ValueAuto)
230         resultHeight = resultWidth * (initialViewportSize.height() / initialViewportSize.width());
231
232     if (type == ViewportArguments::ViewportMeta) {
233         // Extend width and height to fill the visual viewport for the resolved initial-scale.
234         resultWidth = std::max<float>(resultWidth, initialViewportSize.width() / result.initialScale);
235         resultHeight = std::max<float>(resultHeight, initialViewportSize.height() / result.initialScale);
236     }
237
238     result.layoutSize.setWidth(resultWidth);
239     result.layoutSize.setHeight(resultHeight);
240
241     // FIXME: This might affect some ports, but is the right thing to do.
242     // Only set initialScale to a value if it was explicitly set.
243     // if (resultZoom == ViewportArguments::ValueAuto)
244     //    result.initialScale = ViewportArguments::ValueAuto;
245
246     result.userScalable = userZoom;
247     result.orientation = orientation;
248     result.shrinkToFit = shrinkToFit;
249     result.clipToSafeArea = clipToSafeArea;
250
251     return result;
252 }
253
254 static FloatSize convertToUserSpace(const FloatSize& deviceSize, float devicePixelRatio)
255 {
256     FloatSize result = deviceSize;
257     if (devicePixelRatio != 1)
258         result.scale(1 / devicePixelRatio);
259     return result;
260 }
261
262 ViewportAttributes computeViewportAttributes(ViewportArguments args, int desktopWidth, int deviceWidth, int deviceHeight, float devicePixelRatio, IntSize visibleViewport)
263 {
264     FloatSize initialViewportSize = convertToUserSpace(visibleViewport, devicePixelRatio);
265     FloatSize deviceSize = convertToUserSpace(FloatSize(deviceWidth, deviceHeight), devicePixelRatio);
266
267     return args.resolve(initialViewportSize, deviceSize, desktopWidth);
268 }
269
270 float computeMinimumScaleFactorForContentContained(const ViewportAttributes& result, const IntSize& visibleViewport, const IntSize& contentsSize)
271 {
272     FloatSize viewportSize(visibleViewport);
273     return std::max<float>(result.minimumScale, std::max(viewportSize.width() / contentsSize.width(), viewportSize.height() / contentsSize.height()));
274 }
275
276 void restrictMinimumScaleFactorToViewportSize(ViewportAttributes& result, IntSize visibleViewport, float devicePixelRatio)
277 {
278     FloatSize viewportSize = convertToUserSpace(visibleViewport, devicePixelRatio);
279
280     result.minimumScale = std::max<float>(result.minimumScale, std::max(viewportSize.width() / result.layoutSize.width(), viewportSize.height() / result.layoutSize.height()));
281 }
282
283 void restrictScaleFactorToInitialScaleIfNotUserScalable(ViewportAttributes& result)
284 {
285     if (!result.userScalable)
286         result.maximumScale = result.minimumScale = result.initialScale;
287 }
288
289 static void reportViewportWarning(Document&, ViewportErrorCode, StringView replacement1 = { }, StringView replacement2 = { });
290
291 static float numericPrefix(Document& document, StringView key, StringView value, bool* ok = nullptr)
292 {
293     size_t parsedLength;
294     float numericValue;
295     if (value.is8Bit())
296         numericValue = charactersToFloat(value.characters8(), value.length(), parsedLength);
297     else
298         numericValue = charactersToFloat(value.characters16(), value.length(), parsedLength);
299     if (!parsedLength) {
300         reportViewportWarning(document, UnrecognizedViewportArgumentValueError, value, key);
301         if (ok)
302             *ok = false;
303         return 0;
304     }
305     if (parsedLength < value.length())
306         reportViewportWarning(document, TruncatedViewportArgumentValueError, value, key);
307     if (ok)
308         *ok = true;
309     return numericValue;
310 }
311
312 static float findSizeValue(Document& document, StringView key, StringView value, bool* valueWasExplicit = nullptr)
313 {
314     // 1) Non-negative number values are translated to px lengths.
315     // 2) Negative number values are translated to auto.
316     // 3) device-width and device-height are used as keywords.
317     // 4) Other keywords and unknown values translate to 0.0.
318
319     if (valueWasExplicit)
320         *valueWasExplicit = true;
321
322     if (equalLettersIgnoringASCIICase(value, "device-width"))
323         return ViewportArguments::ValueDeviceWidth;
324
325     if (equalLettersIgnoringASCIICase(value, "device-height"))
326         return ViewportArguments::ValueDeviceHeight;
327
328     float sizeValue = numericPrefix(document, key, value);
329
330     if (sizeValue < 0) {
331         if (valueWasExplicit)
332             *valueWasExplicit = false;
333         return ViewportArguments::ValueAuto;
334     }
335
336     return sizeValue;
337 }
338
339 static float findScaleValue(Document& document, StringView key, StringView value)
340 {
341     // 1) Non-negative number values are translated to <number> values.
342     // 2) Negative number values are translated to auto.
343     // 3) yes is translated to 1.0.
344     // 4) device-width and device-height are translated to 10.0.
345     // 5) no and unknown values are translated to 0.0
346
347     if (equalLettersIgnoringASCIICase(value, "yes"))
348         return 1;
349     if (equalLettersIgnoringASCIICase(value, "no"))
350         return 0;
351     if (equalLettersIgnoringASCIICase(value, "device-width"))
352         return 10;
353     if (equalLettersIgnoringASCIICase(value, "device-height"))
354         return 10;
355
356     float numericValue = numericPrefix(document, key, value);
357
358     if (numericValue < 0)
359         return ViewportArguments::ValueAuto;
360
361     if (numericValue > 10.0)
362         reportViewportWarning(document, MaximumScaleTooLargeError);
363
364     return numericValue;
365 }
366
367 static bool findBooleanValue(Document& document, StringView key, StringView value)
368 {
369     // yes and no are used as keywords.
370     // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
371     // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
372
373     if (equalLettersIgnoringASCIICase(value, "yes"))
374         return true;
375     if (equalLettersIgnoringASCIICase(value, "no"))
376         return false;
377     if (equalLettersIgnoringASCIICase(value, "device-width"))
378         return true;
379     if (equalLettersIgnoringASCIICase(value, "device-height"))
380         return true;
381     return std::abs(numericPrefix(document, key, value)) >= 1;
382 }
383
384 void setViewportFeature(ViewportArguments& arguments, Document& document, StringView key, StringView value)
385 {
386     if (equalLettersIgnoringASCIICase(key, "width"))
387         arguments.width = findSizeValue(document, key, value, &arguments.widthWasExplicit);
388     else if (equalLettersIgnoringASCIICase(key, "height"))
389         arguments.height = findSizeValue(document, key, value);
390     else if (equalLettersIgnoringASCIICase(key, "initial-scale"))
391         arguments.zoom = findScaleValue(document, key, value);
392     else if (equalLettersIgnoringASCIICase(key, "minimum-scale"))
393         arguments.minZoom = findScaleValue(document, key, value);
394     else if (equalLettersIgnoringASCIICase(key, "maximum-scale"))
395         arguments.maxZoom = findScaleValue(document, key, value);
396     else if (equalLettersIgnoringASCIICase(key, "user-scalable"))
397         arguments.userZoom = findBooleanValue(document, key, value);
398 #if PLATFORM(IOS)
399     else if (equalLettersIgnoringASCIICase(key, "minimal-ui")) {
400         // FIXME: Ignore silently for now. This code should eventually be removed
401         // so we start giving the warning in the web inspector as for other unimplemented keys.
402     }
403 #endif
404     else if (equalLettersIgnoringASCIICase(key, "shrink-to-fit"))
405         arguments.shrinkToFit = findBooleanValue(document, key, value);
406     else if (equalLettersIgnoringASCIICase(key, "clip-to-safe-area"))
407         arguments.clipToSafeArea = findBooleanValue(document, key, value);
408     else
409         reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, key);
410 }
411
412 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
413 {
414     static const char* const errors[] = {
415         "Viewport argument key \"%replacement1\" not recognized and ignored.",
416         "Viewport argument value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.",
417         "Viewport argument value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
418         "Viewport maximum-scale cannot be larger than 10.0. The maximum-scale will be set to 10.0."
419     };
420
421     return errors[errorCode];
422 }
423
424 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
425 {
426     switch (errorCode) {
427     case TruncatedViewportArgumentValueError:
428         return MessageLevel::Warning;
429     case UnrecognizedViewportArgumentKeyError:
430     case UnrecognizedViewportArgumentValueError:
431     case MaximumScaleTooLargeError:
432         return MessageLevel::Error;
433     }
434
435     ASSERT_NOT_REACHED();
436     return MessageLevel::Error;
437 }
438
439 void reportViewportWarning(Document& document, ViewportErrorCode errorCode, StringView replacement1, StringView replacement2)
440 {
441     // FIXME: Why is this null check needed? Can't addConsoleMessage deal with this?
442     if (!document.frame())
443         return;
444
445     String message = viewportErrorMessageTemplate(errorCode);
446     if (!replacement1.isNull())
447         message.replace("%replacement1", replacement1.toStringWithoutCopying());
448     // FIXME: This will do the wrong thing if replacement1 contains the substring "%replacement2".
449     if (!replacement2.isNull())
450         message.replace("%replacement2", replacement2.toStringWithoutCopying());
451
452     if ((errorCode == UnrecognizedViewportArgumentValueError || errorCode == TruncatedViewportArgumentValueError) && replacement1.contains(';'))
453         message.append(" Note that ';' is not a separator in viewport values. The list should be comma-separated.");
454
455     // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
456     document.addConsoleMessage(MessageSource::Rendering, viewportErrorMessageLevel(errorCode), message);
457 }
458
459 TextStream& operator<<(TextStream& ts, const ViewportArguments& viewportArguments)
460 {
461     ts.increaseIndent();
462
463     ts << "\n";
464     ts.writeIndent();
465     ts << "(width " << viewportArguments.width << ", minWidth " << viewportArguments.minWidth << ", maxWidth " << viewportArguments.maxWidth << ")";
466
467     ts << "\n";
468     ts.writeIndent();
469     ts << "(height " << viewportArguments.height << ", minHeight " << viewportArguments.minHeight << ", maxHeight " << viewportArguments.maxHeight << ")";
470
471     ts << "\n";
472     ts.writeIndent();
473     ts << "(zoom " << viewportArguments.zoom << ", minZoom " << viewportArguments.minZoom << ", maxZoom " << viewportArguments.maxZoom << ")";
474     ts.decreaseIndent();
475
476     return ts;
477 }
478
479 } // namespace WebCore