initial import
[vuplus_webkit] / 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 "PlatformString.h"
38 #include "ScriptableDocumentParser.h"
39
40 using namespace std;
41
42 namespace WebCore {
43
44 ViewportAttributes computeViewportAttributes(ViewportArguments args, int desktopWidth, int deviceWidth, int deviceHeight, int deviceDPI, IntSize visibleViewport)
45 {
46     ViewportAttributes result;
47
48     float availableWidth = visibleViewport.width();
49     float availableHeight = visibleViewport.height();
50
51     ASSERT(availableWidth > 0 && availableHeight > 0);
52
53     switch (int(args.targetDensityDpi)) {
54     case ViewportArguments::ValueDeviceDPI:
55         args.targetDensityDpi = deviceDPI;
56         break;
57     case ViewportArguments::ValueLowDPI:
58         args.targetDensityDpi = 120;
59         break;
60     case ViewportArguments::ValueAuto:
61     case ViewportArguments::ValueMediumDPI:
62         args.targetDensityDpi = 160;
63         break;
64     case ViewportArguments::ValueHighDPI:
65         args.targetDensityDpi = 240;
66         break;
67     }
68
69     result.devicePixelRatio = float(deviceDPI / args.targetDensityDpi);
70
71     // Resolve non-'auto' width and height to pixel values.
72     if (result.devicePixelRatio != 1.0) {
73         availableWidth /= result.devicePixelRatio;
74         availableHeight /= result.devicePixelRatio;
75         deviceWidth /= result.devicePixelRatio;
76         deviceHeight /= result.devicePixelRatio;
77     }
78
79     switch (int(args.width)) {
80     case ViewportArguments::ValueDesktopWidth:
81         args.width = desktopWidth;
82         break;
83     case ViewportArguments::ValueDeviceWidth:
84         args.width = deviceWidth;
85         break;
86     case ViewportArguments::ValueDeviceHeight:
87         args.width = deviceHeight;
88         break;
89     }
90
91     switch (int(args.height)) {
92     case ViewportArguments::ValueDesktopWidth:
93         args.height = desktopWidth;
94         break;
95     case ViewportArguments::ValueDeviceWidth:
96         args.height = deviceWidth;
97         break;
98     case ViewportArguments::ValueDeviceHeight:
99         args.height = deviceHeight;
100         break;
101     }
102
103     // Clamp values to range defined by spec and resolve minimum-scale and maximum-scale values
104     if (args.width != ViewportArguments::ValueAuto)
105         args.width = min(float(10000), max(args.width, float(1)));
106     if (args.height != ViewportArguments::ValueAuto)
107         args.height = min(float(10000), max(args.height, float(1)));
108
109     if (args.initialScale != ViewportArguments::ValueAuto)
110         args.initialScale = min(float(10), max(args.initialScale, float(0.1)));
111     if (args.minimumScale != ViewportArguments::ValueAuto)
112         args.minimumScale = min(float(10), max(args.minimumScale, float(0.1)));
113     if (args.maximumScale != ViewportArguments::ValueAuto)
114         args.maximumScale = min(float(10), max(args.maximumScale, float(0.1)));
115
116     // Resolve minimum-scale and maximum-scale values according to spec.
117     if (args.minimumScale == ViewportArguments::ValueAuto)
118         result.minimumScale = float(0.25);
119     else
120         result.minimumScale = args.minimumScale;
121
122     if (args.maximumScale == ViewportArguments::ValueAuto) {
123         result.maximumScale = float(5.0);
124         result.minimumScale = min(float(5.0), result.minimumScale);
125     } else
126         result.maximumScale = args.maximumScale;
127     result.maximumScale = max(result.minimumScale, result.maximumScale);
128
129     // Resolve initial-scale value.
130     result.initialScale = args.initialScale;
131     if (result.initialScale == ViewportArguments::ValueAuto) {
132         result.initialScale = availableWidth / desktopWidth;
133         if (args.width != ViewportArguments::ValueAuto)
134             result.initialScale = availableWidth / args.width;
135         if (args.height != ViewportArguments::ValueAuto) {
136             // if 'auto', the initial-scale will be negative here and thus ignored.
137             result.initialScale = max(result.initialScale, availableHeight / args.height);
138         }
139     }
140
141     // Constrain initial-scale value to minimum-scale/maximum-scale range.
142     result.initialScale = min(result.maximumScale, max(result.minimumScale, result.initialScale));
143
144     // Resolve width value.
145     float width;
146     if (args.width != ViewportArguments::ValueAuto)
147         width = args.width;
148     else {
149         if (args.initialScale == ViewportArguments::ValueAuto)
150             width = desktopWidth;
151         else if (args.height != ViewportArguments::ValueAuto)
152             width = args.height * (availableWidth / availableHeight);
153         else
154             width = availableWidth / result.initialScale;
155     }
156
157     // Resolve height value.
158     float height;
159     if (args.height != ViewportArguments::ValueAuto)
160         height = args.height;
161     else
162         height = width * availableHeight / availableWidth;
163
164     // Extend width and height to fill the visual viewport for the resolved initial-scale.
165     width = max(width, availableWidth / result.initialScale);
166     height = max(height, availableHeight / result.initialScale);
167     result.layoutSize.setWidth(static_cast<int>(roundf(width)));
168     result.layoutSize.setHeight(static_cast<int>(roundf(height)));
169
170     // Update minimum scale factor, to never allow zooming out more than viewport
171     result.minimumScale = max(result.minimumScale, max(availableWidth / width, availableHeight / height));
172
173     result.userScalable = args.userScalable;
174     // Make maximum and minimum scale equal to the initial scale if user is not allowed to zoom in/out.
175     if (!args.userScalable)
176         result.maximumScale = result.minimumScale = result.initialScale;
177
178     return result;
179 }
180
181 static float numericPrefix(const String& keyString, const String& valueString, Document* document, bool* ok)
182 {
183     // If a prefix of property-value can be converted to a number using strtod,
184     // the value will be that number. The remainder of the string is ignored.
185     // So when String::toFloat says there is an error, it may be a false positive,
186     // and we should check if the valueString prefix was a number.
187
188     bool didReadNumber;
189     float value = valueString.toFloat(ok, &didReadNumber);
190     if (!*ok) {
191         if (!didReadNumber) {
192             ASSERT(!value);
193             reportViewportWarning(document, UnrecognizedViewportArgumentValueError, valueString, keyString);
194             return value;
195         }
196         *ok = true;
197         reportViewportWarning(document, TruncatedViewportArgumentValueError, valueString, keyString);
198     }
199     return value;
200 }
201
202 static float findSizeValue(const String& keyString, const String& valueString, Document* document)
203 {
204     // 1) Non-negative number values are translated to px lengths.
205     // 2) Negative number values are translated to auto.
206     // 3) device-width and device-height are used as keywords.
207     // 4) Other keywords and unknown values translate to 0.0.
208
209     if (equalIgnoringCase(valueString, "desktop-width"))
210         return ViewportArguments::ValueDesktopWidth;
211     if (equalIgnoringCase(valueString, "device-width"))
212         return ViewportArguments::ValueDeviceWidth;
213     if (equalIgnoringCase(valueString, "device-height"))
214         return ViewportArguments::ValueDeviceHeight;
215
216     bool ok;
217     float value = numericPrefix(keyString, valueString, document, &ok);
218     if (!ok)
219         return float(0.0);
220
221     if (value < 0)
222         return ViewportArguments::ValueAuto;
223
224     return value;
225 }
226
227 static float findScaleValue(const String& keyString, const String& valueString, Document* document)
228 {
229     // 1) Non-negative number values are translated to <number> values.
230     // 2) Negative number values are translated to auto.
231     // 3) yes is translated to 1.0.
232     // 4) device-width and device-height are translated to 10.0.
233     // 5) no and unknown values are translated to 0.0
234
235     if (equalIgnoringCase(valueString, "yes"))
236         return float(1.0);
237     if (equalIgnoringCase(valueString, "no"))
238         return float(0.0);
239     if (equalIgnoringCase(valueString, "desktop-width"))
240         return float(10.0);
241     if (equalIgnoringCase(valueString, "device-width"))
242         return float(10.0);
243     if (equalIgnoringCase(valueString, "device-height"))
244         return float(10.0);
245
246     bool ok;
247     float value = numericPrefix(keyString, valueString, document, &ok);
248     if (!ok)
249         return float(0.0);
250
251     if (value < 0)
252         return ViewportArguments::ValueAuto;
253
254     if (value > 10.0)
255         reportViewportWarning(document, MaximumScaleTooLargeError, String(), String());
256
257     return value;
258 }
259
260 static float findUserScalableValue(const String& keyString, const String& valueString, Document* document)
261 {
262     // yes and no are used as keywords.
263     // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
264     // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
265
266     if (equalIgnoringCase(valueString, "yes"))
267         return 1;
268     if (equalIgnoringCase(valueString, "no"))
269         return 0;
270     if (equalIgnoringCase(valueString, "desktop-width"))
271         return 1;
272     if (equalIgnoringCase(valueString, "device-width"))
273         return 1;
274     if (equalIgnoringCase(valueString, "device-height"))
275         return 1;
276
277     bool ok;
278     float value = numericPrefix(keyString, valueString, document, &ok);
279     if (!ok)
280         return 0;
281
282     if (fabs(value) < 1)
283         return 0;
284
285     return 1;
286 }
287
288 static float findTargetDensityDPIValue(const String& keyString, const String& valueString, Document* document)
289 {
290     if (equalIgnoringCase(valueString, "device-dpi"))
291         return ViewportArguments::ValueDeviceDPI;
292     if (equalIgnoringCase(valueString, "low-dpi"))
293         return ViewportArguments::ValueLowDPI;
294     if (equalIgnoringCase(valueString, "medium-dpi"))
295         return ViewportArguments::ValueMediumDPI;
296     if (equalIgnoringCase(valueString, "high-dpi"))
297         return ViewportArguments::ValueHighDPI;
298
299     bool ok;
300     float value = numericPrefix(keyString, valueString, document, &ok);
301     if (!ok)
302         return ViewportArguments::ValueAuto;
303
304      if (value < 70 || value > 400) {
305         reportViewportWarning(document, TargetDensityDpiTooSmallOrLargeError, String(), String());
306         return ViewportArguments::ValueAuto;
307     }
308
309     return value;
310 }
311
312 void setViewportFeature(const String& keyString, const String& valueString, Document* document, void* data)
313 {
314     ViewportArguments* arguments = static_cast<ViewportArguments*>(data);
315
316     if (keyString == "width")
317         arguments->width = findSizeValue(keyString, valueString, document);
318     else if (keyString == "height")
319         arguments->height = findSizeValue(keyString, valueString, document);
320     else if (keyString == "initial-scale")
321         arguments->initialScale = findScaleValue(keyString, valueString, document);
322     else if (keyString == "minimum-scale")
323         arguments->minimumScale = findScaleValue(keyString, valueString, document);
324     else if (keyString == "maximum-scale")
325         arguments->maximumScale = findScaleValue(keyString, valueString, document);
326     else if (keyString == "user-scalable")
327         arguments->userScalable = findUserScalableValue(keyString, valueString, document);
328     else if (keyString == "target-densitydpi")
329         arguments->targetDensityDpi = findTargetDensityDPIValue(keyString, valueString, document);
330     else
331         reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, keyString, String());
332 }
333
334 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
335 {
336     static const char* const errors[] = {
337         "Viewport argument key \"%replacement1\" not recognized and ignored.",
338         "Viewport argument value \"%replacement1\" for key \"%replacement2\" not recognized. Content ignored.",
339         "Viewport argument value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
340         "Viewport maximum-scale cannot be larger than 10.0. The maximum-scale will be set to 10.0.",
341         "Viewport target-densitydpi has to take a number between 70 and 400 as a valid target dpi, try using \"device-dpi\", \"low-dpi\", \"medium-dpi\" or \"high-dpi\" instead for future compatibility."
342     };
343
344     return errors[errorCode];
345 }
346
347 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
348 {
349     switch (errorCode) {
350     case TruncatedViewportArgumentValueError:
351     case TargetDensityDpiTooSmallOrLargeError:
352         return TipMessageLevel;
353     case UnrecognizedViewportArgumentKeyError:
354     case UnrecognizedViewportArgumentValueError:
355     case MaximumScaleTooLargeError:
356         return ErrorMessageLevel;
357     }
358
359     ASSERT_NOT_REACHED();
360     return ErrorMessageLevel;
361 }
362
363 // FIXME: Why is this different from SVGDocumentExtensions parserLineNumber?
364 // FIXME: Callers should probably use ScriptController::eventHandlerLineNumber()
365 static int parserLineNumber(Document* document)
366 {
367     if (!document)
368         return 0;
369     ScriptableDocumentParser* parser = document->scriptableDocumentParser();
370     if (!parser)
371         return 0;
372     return parser->lineNumber() + 1;
373 }
374
375 void reportViewportWarning(Document* document, ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
376 {
377     Frame* frame = document->frame();
378     if (!frame)
379         return;
380
381     String message = viewportErrorMessageTemplate(errorCode);
382     if (!replacement1.isNull())
383         message.replace("%replacement1", replacement1);
384     if (!replacement2.isNull())
385         message.replace("%replacement2", replacement2);
386
387     frame->domWindow()->console()->addMessage(HTMLMessageSource, LogMessageType, viewportErrorMessageLevel(errorCode), message, parserLineNumber(document), document->url().string());
388 }
389
390 } // namespace WebCore