Remove support for "desktop-width" in the viewport meta tag
[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  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB.  If not, write to
22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  *
25  */
26
27 #include "config.h"
28 #include "ViewportArguments.h"
29
30 #include "Chrome.h"
31 #include "Console.h"
32 #include "DOMWindow.h"
33 #include "Document.h"
34 #include "Frame.h"
35 #include "IntSize.h"
36 #include "Page.h"
37 #include "ScriptableDocumentParser.h"
38 #include <wtf/text/WTFString.h>
39
40 using namespace std;
41
42 namespace WebCore {
43
44 const float ViewportArguments::deprecatedTargetDPI = 160;
45
46 ViewportAttributes computeViewportAttributes(ViewportArguments args, int desktopWidth, int deviceWidth, int deviceHeight, float devicePixelRatio, IntSize visibleViewport)
47 {
48     ViewportAttributes result;
49
50     float availableWidth = visibleViewport.width();
51     float availableHeight = visibleViewport.height();
52
53     ASSERT(availableWidth > 0 && availableHeight > 0);
54
55     // Resolve non-'auto' width and height to pixel values.
56     if (devicePixelRatio != 1.0) {
57         availableWidth /= devicePixelRatio;
58         availableHeight /= devicePixelRatio;
59         deviceWidth /= devicePixelRatio;
60         deviceHeight /= devicePixelRatio;
61     }
62
63     switch (int(args.width)) {
64     case ViewportArguments::ValueDeviceWidth:
65         args.width = deviceWidth;
66         break;
67     case ViewportArguments::ValueDeviceHeight:
68         args.width = deviceHeight;
69         break;
70     }
71
72     switch (int(args.height)) {
73     case ViewportArguments::ValueDeviceWidth:
74         args.height = deviceWidth;
75         break;
76     case ViewportArguments::ValueDeviceHeight:
77         args.height = deviceHeight;
78         break;
79     }
80
81     // Clamp values to range defined by spec and resolve minimum-scale and maximum-scale values
82     if (args.width != ViewportArguments::ValueAuto)
83         args.width = min(float(10000), max(args.width, float(1)));
84     if (args.height != ViewportArguments::ValueAuto)
85         args.height = min(float(10000), max(args.height, float(1)));
86
87     result.initiallyFitToViewport = args.initialScale == ViewportArguments::ValueAuto;
88
89     if (args.initialScale != ViewportArguments::ValueAuto)
90         args.initialScale = min(float(10), max(args.initialScale, float(0.1)));
91     if (args.minimumScale != ViewportArguments::ValueAuto)
92         args.minimumScale = min(float(10), max(args.minimumScale, float(0.1)));
93     if (args.maximumScale != ViewportArguments::ValueAuto)
94         args.maximumScale = min(float(10), max(args.maximumScale, float(0.1)));
95
96     // Resolve minimum-scale and maximum-scale values according to spec.
97     if (args.minimumScale == ViewportArguments::ValueAuto)
98         result.minimumScale = float(0.25);
99     else
100         result.minimumScale = args.minimumScale;
101
102     if (args.maximumScale == ViewportArguments::ValueAuto) {
103         result.maximumScale = float(5.0);
104         result.minimumScale = min(float(5.0), result.minimumScale);
105     } else
106         result.maximumScale = args.maximumScale;
107     result.maximumScale = max(result.minimumScale, result.maximumScale);
108
109     // Resolve initial-scale value.
110     result.initialScale = args.initialScale;
111     if (result.initialScale == ViewportArguments::ValueAuto) {
112         result.initialScale = availableWidth / desktopWidth;
113         if (args.width != ViewportArguments::ValueAuto)
114             result.initialScale = availableWidth / args.width;
115         if (args.height != ViewportArguments::ValueAuto) {
116             // if 'auto', the initial-scale will be negative here and thus ignored.
117             result.initialScale = max<float>(result.initialScale, availableHeight / args.height);
118         }
119     }
120
121     // Constrain initial-scale value to minimum-scale/maximum-scale range.
122     result.initialScale = min(result.maximumScale, max(result.minimumScale, result.initialScale));
123
124     // Resolve width value.
125     float width;
126     if (args.width != ViewportArguments::ValueAuto)
127         width = args.width;
128     else {
129         if (args.initialScale == ViewportArguments::ValueAuto)
130             width = desktopWidth;
131         else if (args.height != ViewportArguments::ValueAuto)
132             width = args.height * (availableWidth / availableHeight);
133         else
134             width = availableWidth / result.initialScale;
135     }
136
137     // Resolve height value.
138     float height;
139     if (args.height != ViewportArguments::ValueAuto)
140         height = args.height;
141     else
142         height = width * availableHeight / availableWidth;
143
144     // Extend width and height to fill the visual viewport for the resolved initial-scale.
145     width = max<float>(width, availableWidth / result.initialScale);
146     height = max<float>(height, availableHeight / result.initialScale);
147     result.layoutSize.setWidth(width);
148     result.layoutSize.setHeight(height);
149
150     result.userScalable = args.userScalable;
151
152     return result;
153 }
154
155 float computeMinimumScaleFactorForContentContained(const ViewportAttributes& result, const IntSize& viewportSize, const IntSize& contentsSize, float devicePixelRatio)
156 {
157     float availableWidth = viewportSize.width();
158     float availableHeight = viewportSize.height();
159
160     if (devicePixelRatio != 1.0) {
161         availableWidth /= devicePixelRatio;
162         availableHeight /= devicePixelRatio;
163     }
164
165     return max<float>(result.minimumScale, max(availableWidth / contentsSize.width(), availableHeight / contentsSize.height()));
166 }
167
168 void restrictMinimumScaleFactorToViewportSize(ViewportAttributes& result, IntSize visibleViewport, float devicePixelRatio)
169 {
170     float availableWidth = visibleViewport.width();
171     float availableHeight = visibleViewport.height();
172
173     if (devicePixelRatio != 1.0) {
174         availableWidth /= devicePixelRatio;
175         availableHeight /= devicePixelRatio;
176     }
177
178     result.minimumScale = max<float>(result.minimumScale, max(availableWidth / result.layoutSize.width(), availableHeight / result.layoutSize.height()));
179 }
180
181 void restrictScaleFactorToInitialScaleIfNotUserScalable(ViewportAttributes& result)
182 {
183     if (!result.userScalable)
184         result.maximumScale = result.minimumScale = result.initialScale;
185 }
186
187 static float numericPrefix(const String& keyString, const String& valueString, Document* document, bool* ok = 0)
188 {
189     size_t parsedLength;
190     float value;
191     if (valueString.is8Bit())
192         value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength);
193     else
194         value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength);
195     if (!parsedLength) {
196         reportViewportWarning(document, UnrecognizedViewportArgumentValueError, valueString, keyString);
197         if (ok)
198             *ok = false;
199         return 0;
200     }
201     if (parsedLength < valueString.length())
202         reportViewportWarning(document, TruncatedViewportArgumentValueError, valueString, keyString);
203     if (ok)
204         *ok = true;
205     return value;
206 }
207
208 static float findSizeValue(const String& keyString, const String& valueString, Document* document)
209 {
210     // 1) Non-negative number values are translated to px lengths.
211     // 2) Negative number values are translated to auto.
212     // 3) device-width and device-height are used as keywords.
213     // 4) Other keywords and unknown values translate to 0.0.
214
215     if (equalIgnoringCase(valueString, "device-width"))
216         return ViewportArguments::ValueDeviceWidth;
217     if (equalIgnoringCase(valueString, "device-height"))
218         return ViewportArguments::ValueDeviceHeight;
219
220     float value = numericPrefix(keyString, valueString, document);
221
222     if (value < 0)
223         return ViewportArguments::ValueAuto;
224
225     return value;
226 }
227
228 static float findScaleValue(const String& keyString, const String& valueString, Document* document)
229 {
230     // 1) Non-negative number values are translated to <number> values.
231     // 2) Negative number values are translated to auto.
232     // 3) yes is translated to 1.0.
233     // 4) device-width and device-height are translated to 10.0.
234     // 5) no and unknown values are translated to 0.0
235
236     if (equalIgnoringCase(valueString, "yes"))
237         return 1;
238     if (equalIgnoringCase(valueString, "no"))
239         return 0;
240     if (equalIgnoringCase(valueString, "device-width"))
241         return 10;
242     if (equalIgnoringCase(valueString, "device-height"))
243         return 10;
244
245     float value = numericPrefix(keyString, valueString, document);
246
247     if (value < 0)
248         return ViewportArguments::ValueAuto;
249
250     if (value > 10.0)
251         reportViewportWarning(document, MaximumScaleTooLargeError, String(), String());
252
253     return value;
254 }
255
256 static float findUserScalableValue(const String& keyString, const String& valueString, Document* document)
257 {
258     // yes and no are used as keywords.
259     // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
260     // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
261
262     if (equalIgnoringCase(valueString, "yes"))
263         return 1;
264     if (equalIgnoringCase(valueString, "no"))
265         return 0;
266     if (equalIgnoringCase(valueString, "device-width"))
267         return 1;
268     if (equalIgnoringCase(valueString, "device-height"))
269         return 1;
270
271     float value = numericPrefix(keyString, valueString, document);
272
273     if (fabs(value) < 1)
274         return 0;
275
276     return 1;
277 }
278
279 void setViewportFeature(const String& keyString, const String& valueString, Document* document, void* data)
280 {
281     ViewportArguments* arguments = static_cast<ViewportArguments*>(data);
282
283     if (keyString == "width")
284         arguments->width = findSizeValue(keyString, valueString, document);
285     else if (keyString == "height")
286         arguments->height = findSizeValue(keyString, valueString, document);
287     else if (keyString == "initial-scale")
288         arguments->initialScale = findScaleValue(keyString, valueString, document);
289     else if (keyString == "minimum-scale")
290         arguments->minimumScale = findScaleValue(keyString, valueString, document);
291     else if (keyString == "maximum-scale")
292         arguments->maximumScale = findScaleValue(keyString, valueString, document);
293     else if (keyString == "user-scalable")
294         arguments->userScalable = findUserScalableValue(keyString, valueString, document);
295     else if (keyString == "target-densitydpi")
296         reportViewportWarning(document, TargetDensityDpiUnsupported, String(), String());
297     else
298         reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, keyString, String());
299 }
300
301 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
302 {
303     static const char* const errors[] = {
304         "Viewport argument key \"%replacement1\" not recognized and ignored.",
305         "Viewport argument value \"%replacement1\" for key \"%replacement2\" not recognized. Content ignored.",
306         "Viewport argument value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
307         "Viewport maximum-scale cannot be larger than 10.0. The maximum-scale will be set to 10.0.",
308         "Viewport target-densitydpi is not supported.",
309     };
310
311     return errors[errorCode];
312 }
313
314 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
315 {
316     switch (errorCode) {
317     case TruncatedViewportArgumentValueError:
318     case TargetDensityDpiUnsupported:
319         return TipMessageLevel;
320     case UnrecognizedViewportArgumentKeyError:
321     case UnrecognizedViewportArgumentValueError:
322     case MaximumScaleTooLargeError:
323         return ErrorMessageLevel;
324     }
325
326     ASSERT_NOT_REACHED();
327     return ErrorMessageLevel;
328 }
329
330 // FIXME: Why is this different from SVGDocumentExtensions parserLineNumber?
331 // FIXME: Callers should probably use ScriptController::eventHandlerLineNumber()
332 static int parserLineNumber(Document* document)
333 {
334     if (!document)
335         return 0;
336     ScriptableDocumentParser* parser = document->scriptableDocumentParser();
337     if (!parser)
338         return 0;
339     return parser->lineNumber().oneBasedInt();
340 }
341
342 void reportViewportWarning(Document* document, ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
343 {
344     Frame* frame = document->frame();
345     if (!frame)
346         return;
347
348     String message = viewportErrorMessageTemplate(errorCode);
349     if (!replacement1.isNull())
350         message.replace("%replacement1", replacement1);
351     if (!replacement2.isNull())
352         message.replace("%replacement2", replacement2);
353
354     if ((errorCode == UnrecognizedViewportArgumentValueError || errorCode == TruncatedViewportArgumentValueError) && replacement1.find(';') != WTF::notFound)
355         message.append(" Note that ';' is not a separator in viewport values. The list should be comma-separated.");
356
357     document->domWindow()->console()->addMessage(HTMLMessageSource, LogMessageType, viewportErrorMessageLevel(errorCode), message, document->url().string(), parserLineNumber(document));
358 }
359
360 } // namespace WebCore