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