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
computeViewportAttributes(ViewportArguments args,int desktopWidth,int deviceWidth,int deviceHeight,int deviceDPI,IntSize visibleViewport)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(width);
168 result.layoutSize.setHeight(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
numericPrefix(const String & keyString,const String & valueString,Document * document,bool * ok)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
findSizeValue(const String & keyString,const String & valueString,Document * document)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
findScaleValue(const String & keyString,const String & valueString,Document * document)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
findUserScalableValue(const String & keyString,const String & valueString,Document * document)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
findTargetDensityDPIValue(const String & keyString,const String & valueString,Document * document)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
setViewportFeature(const String & keyString,const String & valueString,Document * document,void * data)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
viewportErrorMessageTemplate(ViewportErrorCode errorCode)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
viewportErrorMessageLevel(ViewportErrorCode errorCode)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()
parserLineNumber(Document * document)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
reportViewportWarning(Document * document,ViewportErrorCode errorCode,const String & replacement1,const String & replacement2)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
391