Replace WTF::move with WTFMove
[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 "Page.h"
35 #include "ScriptableDocumentParser.h"
36 #include "TextStream.h"
37
38 namespace WebCore {
39
40 #if PLATFORM(GTK)
41 const float ViewportArguments::deprecatedTargetDPI = 160;
42 #endif
43
44 static const float& compareIgnoringAuto(const float& value1, const float& value2, const float& (*compare) (const float&, const float&))
45 {
46     ASSERT(value1 != ViewportArguments::ValueAuto || value2 != ViewportArguments::ValueAuto);
47
48     if (value1 == ViewportArguments::ValueAuto)
49         return value2;
50
51     if (value2 == ViewportArguments::ValueAuto)
52         return value1;
53
54     return compare(value1, value2);
55 }
56
57 static inline float clampLengthValue(float value)
58 {
59     ASSERT(value != ViewportArguments::ValueDeviceWidth);
60     ASSERT(value != ViewportArguments::ValueDeviceHeight);
61
62     // Limits as defined in the css-device-adapt spec.
63     if (value != ViewportArguments::ValueAuto)
64         return std::min<float>(10000, std::max<float>(value, 1));
65     return value;
66 }
67
68 static inline float clampScaleValue(float value)
69 {
70     ASSERT(value != ViewportArguments::ValueDeviceWidth);
71     ASSERT(value != ViewportArguments::ValueDeviceHeight);
72
73     // Limits as defined in the css-device-adapt spec.
74     if (value != ViewportArguments::ValueAuto)
75         return std::min<float>(10, std::max<float>(value, 0.1));
76     return value;
77 }
78
79 ViewportAttributes ViewportArguments::resolve(const FloatSize& initialViewportSize, const FloatSize& deviceSize, int defaultWidth) const
80 {
81     float resultWidth = width;
82     float resultMaxWidth = maxWidth;
83     float resultMinWidth = minWidth;
84     float resultHeight = height;
85     float resultMinHeight = minHeight;
86     float resultMaxHeight = maxHeight;
87     float resultZoom = zoom;
88     float resultMinZoom = minZoom;
89     float resultMaxZoom = maxZoom;
90
91     switch (int(resultWidth)) {
92     case ViewportArguments::ValueDeviceWidth:
93         resultWidth = deviceSize.width();
94         break;
95     case ViewportArguments::ValueDeviceHeight:
96         resultWidth = deviceSize.height();
97         break;
98     }
99
100     switch (int(resultHeight)) {
101     case ViewportArguments::ValueDeviceWidth:
102         resultHeight = deviceSize.width();
103         break;
104     case ViewportArguments::ValueDeviceHeight:
105         resultHeight = deviceSize.height();
106         break;
107     }
108
109     if (type == ViewportArguments::CSSDeviceAdaptation) {
110         switch (int(resultMinWidth)) {
111         case ViewportArguments::ValueDeviceWidth:
112             resultMinWidth = deviceSize.width();
113             break;
114         case ViewportArguments::ValueDeviceHeight:
115             resultMinWidth = deviceSize.height();
116             break;
117         }
118
119         switch (int(resultMaxWidth)) {
120         case ViewportArguments::ValueDeviceWidth:
121             resultMaxWidth = deviceSize.width();
122             break;
123         case ViewportArguments::ValueDeviceHeight:
124             resultMaxWidth = deviceSize.height();
125             break;
126         }
127
128         switch (int(resultMinHeight)) {
129         case ViewportArguments::ValueDeviceWidth:
130             resultMinHeight = deviceSize.width();
131             break;
132         case ViewportArguments::ValueDeviceHeight:
133             resultMinHeight = deviceSize.height();
134             break;
135         }
136
137         switch (int(resultMaxHeight)) {
138         case ViewportArguments::ValueDeviceWidth:
139             resultMaxHeight = deviceSize.width();
140             break;
141         case ViewportArguments::ValueDeviceHeight:
142             resultMaxHeight = deviceSize.height();
143             break;
144         }
145
146         if (resultMinWidth != ViewportArguments::ValueAuto || resultMaxWidth != ViewportArguments::ValueAuto)
147             resultWidth = compareIgnoringAuto(resultMinWidth, compareIgnoringAuto(resultMaxWidth, deviceSize.width(), std::min), std::max);
148
149         if (resultMinHeight != ViewportArguments::ValueAuto || resultMaxHeight != ViewportArguments::ValueAuto)
150             resultHeight = compareIgnoringAuto(resultMinHeight, compareIgnoringAuto(resultMaxHeight, deviceSize.height(), std::min), std::max);
151
152         if (resultMinZoom != ViewportArguments::ValueAuto && resultMaxZoom != ViewportArguments::ValueAuto)
153             resultMaxZoom = std::max(resultMinZoom, resultMaxZoom);
154
155         if (resultZoom != ViewportArguments::ValueAuto)
156             resultZoom = compareIgnoringAuto(resultMinZoom, compareIgnoringAuto(resultMaxZoom, resultZoom, std::min), std::max);
157
158         if (resultWidth == ViewportArguments::ValueAuto && resultZoom == ViewportArguments::ValueAuto)
159             resultWidth = deviceSize.width();
160
161         if (resultWidth == ViewportArguments::ValueAuto && resultHeight == ViewportArguments::ValueAuto)
162             resultWidth = deviceSize.width() / resultZoom;
163
164         if (resultWidth == ViewportArguments::ValueAuto)
165             resultWidth = resultHeight * deviceSize.width() / deviceSize.height();
166
167         if (resultHeight == ViewportArguments::ValueAuto)
168             resultHeight = resultWidth * deviceSize.height() / deviceSize.width();
169
170         if (resultZoom != ViewportArguments::ValueAuto || resultMaxZoom != ViewportArguments::ValueAuto) {
171             resultWidth = compareIgnoringAuto(resultWidth, deviceSize.width() / compareIgnoringAuto(resultZoom, resultMaxZoom, std::min), std::max);
172             resultHeight = compareIgnoringAuto(resultHeight, deviceSize.height() / compareIgnoringAuto(resultZoom, resultMaxZoom, std::min), std::max);
173         }
174
175         resultWidth = std::max<float>(1, resultWidth);
176         resultHeight = std::max<float>(1, resultHeight);
177     }
178
179     if (type != ViewportArguments::CSSDeviceAdaptation && type != ViewportArguments::Implicit) {
180         // Clamp values to a valid range, but not for @viewport since is
181         // not mandated by the specification.
182         resultWidth = clampLengthValue(resultWidth);
183         resultHeight = clampLengthValue(resultHeight);
184         resultZoom = clampScaleValue(resultZoom);
185         resultMinZoom = clampScaleValue(resultMinZoom);
186         resultMaxZoom = clampScaleValue(resultMaxZoom);
187     }
188
189     ViewportAttributes result;
190
191     // Resolve minimum-scale and maximum-scale values according to spec.
192     if (resultMinZoom == ViewportArguments::ValueAuto)
193         result.minimumScale = float(0.25);
194     else
195         result.minimumScale = resultMinZoom;
196
197     if (resultMaxZoom == ViewportArguments::ValueAuto) {
198         result.maximumScale = 5;
199         result.minimumScale = std::min<float>(5, result.minimumScale);
200     } else
201         result.maximumScale = resultMaxZoom;
202     result.maximumScale = std::max(result.minimumScale, result.maximumScale);
203
204     // Resolve initial-scale value.
205     result.initialScale = resultZoom;
206     if (resultZoom == ViewportArguments::ValueAuto) {
207         result.initialScale = initialViewportSize.width() / defaultWidth;
208         if (resultWidth != ViewportArguments::ValueAuto)
209             result.initialScale = initialViewportSize.width() / resultWidth;
210         if (resultHeight != ViewportArguments::ValueAuto) {
211             // if 'auto', the initial-scale will be negative here and thus ignored.
212             result.initialScale = std::max<float>(result.initialScale, initialViewportSize.height() / resultHeight);
213         }
214     }
215
216     // Constrain initial-scale value to minimum-scale/maximum-scale range.
217     result.initialScale = std::min(result.maximumScale, std::max(result.minimumScale, result.initialScale));
218
219     // Resolve width value.
220     if (resultWidth == ViewportArguments::ValueAuto) {
221         if (resultZoom == ViewportArguments::ValueAuto)
222             resultWidth = defaultWidth;
223         else if (resultHeight != ViewportArguments::ValueAuto)
224             resultWidth = resultHeight * (initialViewportSize.width() / initialViewportSize.height());
225         else
226             resultWidth = initialViewportSize.width() / result.initialScale;
227     }
228
229     // Resolve height value.
230     if (resultHeight == ViewportArguments::ValueAuto)
231         resultHeight = resultWidth * (initialViewportSize.height() / initialViewportSize.width());
232
233     if (type == ViewportArguments::ViewportMeta) {
234         // Extend width and height to fill the visual viewport for the resolved initial-scale.
235         resultWidth = std::max<float>(resultWidth, initialViewportSize.width() / result.initialScale);
236         resultHeight = std::max<float>(resultHeight, initialViewportSize.height() / result.initialScale);
237     }
238
239     result.layoutSize.setWidth(resultWidth);
240     result.layoutSize.setHeight(resultHeight);
241
242     // FIXME: This might affect some ports, but is the right thing to do.
243     // Only set initialScale to a value if it was explicitly set.
244     // if (resultZoom == ViewportArguments::ValueAuto)
245     //    result.initialScale = ViewportArguments::ValueAuto;
246
247     result.userScalable = userZoom;
248     result.orientation = orientation;
249     result.shrinkToFit = shrinkToFit;
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 float numericPrefix(const String& keyString, const String& valueString, Document* document, bool* ok = nullptr)
290 {
291     size_t parsedLength;
292     float value;
293     if (valueString.is8Bit())
294         value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength);
295     else
296         value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength);
297     if (!parsedLength) {
298         reportViewportWarning(document, UnrecognizedViewportArgumentValueError, valueString, keyString);
299         if (ok)
300             *ok = false;
301         return 0;
302     }
303     if (parsedLength < valueString.length())
304         reportViewportWarning(document, TruncatedViewportArgumentValueError, valueString, keyString);
305     if (ok)
306         *ok = true;
307     return value;
308 }
309
310 static float findSizeValue(const String& keyString, const String& valueString, Document* document)
311 {
312     // 1) Non-negative number values are translated to px lengths.
313     // 2) Negative number values are translated to auto.
314     // 3) device-width and device-height are used as keywords.
315     // 4) Other keywords and unknown values translate to 0.0.
316
317     if (equalIgnoringCase(valueString, "device-width"))
318         return ViewportArguments::ValueDeviceWidth;
319     if (equalIgnoringCase(valueString, "device-height"))
320         return ViewportArguments::ValueDeviceHeight;
321
322     float value = numericPrefix(keyString, valueString, document);
323
324     if (value < 0)
325         return ViewportArguments::ValueAuto;
326
327     return value;
328 }
329
330 static float findScaleValue(const String& keyString, const String& valueString, Document* document)
331 {
332     // 1) Non-negative number values are translated to <number> values.
333     // 2) Negative number values are translated to auto.
334     // 3) yes is translated to 1.0.
335     // 4) device-width and device-height are translated to 10.0.
336     // 5) no and unknown values are translated to 0.0
337
338     if (equalIgnoringCase(valueString, "yes"))
339         return 1;
340     if (equalIgnoringCase(valueString, "no"))
341         return 0;
342     if (equalIgnoringCase(valueString, "device-width"))
343         return 10;
344     if (equalIgnoringCase(valueString, "device-height"))
345         return 10;
346
347     float value = numericPrefix(keyString, valueString, document);
348
349     if (value < 0)
350         return ViewportArguments::ValueAuto;
351
352     if (value > 10.0)
353         reportViewportWarning(document, MaximumScaleTooLargeError, String(), String());
354
355     return value;
356 }
357
358 static float findBooleanValue(const String& keyString, const String& valueString, Document* document)
359 {
360     // yes and no are used as keywords.
361     // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
362     // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
363
364     if (equalIgnoringCase(valueString, "yes"))
365         return 1;
366     if (equalIgnoringCase(valueString, "no"))
367         return 0;
368     if (equalIgnoringCase(valueString, "device-width"))
369         return 1;
370     if (equalIgnoringCase(valueString, "device-height"))
371         return 1;
372
373     float value = numericPrefix(keyString, valueString, document);
374
375     if (fabs(value) < 1)
376         return 0;
377
378     return 1;
379 }
380
381 void setViewportFeature(const String& keyString, const String& valueString, Document* document, void* data)
382 {
383     ViewportArguments* arguments = static_cast<ViewportArguments*>(data);
384
385     if (keyString == "width")
386         arguments->width = findSizeValue(keyString, valueString, document);
387     else if (keyString == "height")
388         arguments->height = findSizeValue(keyString, valueString, document);
389     else if (keyString == "initial-scale")
390         arguments->zoom = findScaleValue(keyString, valueString, document);
391     else if (keyString == "minimum-scale")
392         arguments->minZoom = findScaleValue(keyString, valueString, document);
393     else if (keyString == "maximum-scale")
394         arguments->maxZoom = findScaleValue(keyString, valueString, document);
395     else if (keyString == "user-scalable")
396         arguments->userZoom = findBooleanValue(keyString, valueString, document);
397 #if PLATFORM(IOS)
398     else if (keyString == "minimal-ui")
399         // FIXME: Ignore silently for now. This should eventually fall back to the warning.
400         { }
401 #endif
402     else if (keyString == "shrink-to-fit")
403         arguments->shrinkToFit = findBooleanValue(keyString, valueString, document);
404     else
405         reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, keyString, String());
406 }
407
408 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
409 {
410     static const char* const errors[] = {
411         "Viewport argument key \"%replacement1\" not recognized and ignored.",
412         "Viewport argument value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.",
413         "Viewport argument value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
414         "Viewport maximum-scale cannot be larger than 10.0. The maximum-scale will be set to 10.0."
415     };
416
417     return errors[errorCode];
418 }
419
420 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
421 {
422     switch (errorCode) {
423     case TruncatedViewportArgumentValueError:
424         return MessageLevel::Warning;
425     case UnrecognizedViewportArgumentKeyError:
426     case UnrecognizedViewportArgumentValueError:
427     case MaximumScaleTooLargeError:
428         return MessageLevel::Error;
429     }
430
431     ASSERT_NOT_REACHED();
432     return MessageLevel::Error;
433 }
434
435 void reportViewportWarning(Document* document, ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
436 {
437     Frame* frame = document->frame();
438     if (!frame)
439         return;
440
441     String message = viewportErrorMessageTemplate(errorCode);
442     if (!replacement1.isNull())
443         message.replace("%replacement1", replacement1);
444     if (!replacement2.isNull())
445         message.replace("%replacement2", replacement2);
446
447     if ((errorCode == UnrecognizedViewportArgumentValueError || errorCode == TruncatedViewportArgumentValueError) && replacement1.find(';') != WTF::notFound)
448         message.append(" Note that ';' is not a separator in viewport values. The list should be comma-separated.");
449
450     // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
451     document->addConsoleMessage(MessageSource::Rendering, viewportErrorMessageLevel(errorCode), message);
452 }
453
454 TextStream& operator<<(TextStream& ts, const ViewportArguments& viewportArguments)
455 {
456     ts.increaseIndent();
457
458     ts << "\n";
459     ts.writeIndent();
460     ts << "(width " << viewportArguments.width << ", minWidth " << viewportArguments.minWidth << ", maxWidth " << viewportArguments.maxWidth << ")";
461
462     ts << "\n";
463     ts.writeIndent();
464     ts << "(height " << viewportArguments.height << ", minHeight " << viewportArguments.minHeight << ", maxHeight " << viewportArguments.maxHeight << ")";
465
466     ts << "\n";
467     ts.writeIndent();
468     ts << "(zoom " << viewportArguments.zoom << ", minZoom " << viewportArguments.minZoom << ", maxZoom " << viewportArguments.maxZoom << ")";
469     ts.decreaseIndent();
470
471     return ts;
472 }
473
474 } // namespace WebCore