1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * (C) 2006 Alexey Proskuryakov (ap (at) 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(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 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 391