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