| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) | 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) | 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| 4 * (C) 2001 Dirk Mueller (mueller@kde.org) | 4 * (C) 2001 Dirk Mueller (mueller@kde.org) |
| 5 * Copyright (C) 2003, 2010 Apple Inc. All rights reserved. | 5 * Copyright (C) 2003, 2010 Apple Inc. All rights reserved. |
| 6 * | 6 * |
| 7 * This library is free software; you can redistribute it and/or | 7 * This library is free software; you can redistribute it and/or |
| 8 * modify it under the terms of the GNU Library General Public | 8 * modify it under the terms of the GNU Library General Public |
| 9 * License as published by the Free Software Foundation; either | 9 * License as published by the Free Software Foundation; either |
| 10 * version 2 of the License, or (at your option) any later version. | 10 * version 2 of the License, or (at your option) any later version. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 #include "core/dom/Document.h" | 27 #include "core/dom/Document.h" |
| 28 #include "core/dom/ElementTraversal.h" | 28 #include "core/dom/ElementTraversal.h" |
| 29 #include "core/frame/LocalFrame.h" | 29 #include "core/frame/LocalFrame.h" |
| 30 #include "core/frame/Settings.h" | 30 #include "core/frame/Settings.h" |
| 31 #include "core/inspector/ConsoleMessage.h" | 31 #include "core/inspector/ConsoleMessage.h" |
| 32 #include "core/loader/FrameLoaderClient.h" | 32 #include "core/loader/FrameLoaderClient.h" |
| 33 #include "platform/RuntimeEnabledFeatures.h" | 33 #include "platform/RuntimeEnabledFeatures.h" |
| 34 | 34 |
| 35 namespace blink { | 35 namespace blink { |
| 36 | 36 |
| 37 #define DEFINE_ARRAY_FOR_MATCHING(name, source, maxMatchLength) \ | |
| 38 const UChar* name; \ | |
| 39 const unsigned uMaxMatchLength = maxMatchLength; \ | |
| 40 UChar characterBuffer[uMaxMatchLength]; \ | |
| 41 if (!source.is8Bit()) { \ | |
| 42 name = source.characters16(); \ | |
| 43 } else { \ | |
| 44 unsigned bufferLength = std::min(uMaxMatchLength, source.length()); \ | |
| 45 const LChar* characters8 = source.characters8(); \ | |
| 46 for (unsigned i = 0; i < bufferLength; ++i) \ | |
| 47 characterBuffer[i] = characters8[i]; \ | |
| 48 name = characterBuffer; \ | |
| 49 } | |
| 50 | |
| 51 inline HTMLMetaElement::HTMLMetaElement(Document& document) | 37 inline HTMLMetaElement::HTMLMetaElement(Document& document) |
| 52 : HTMLElement(HTMLNames::metaTag, document) | 38 : HTMLElement(HTMLNames::metaTag, document) |
| 53 { | 39 { |
| 54 ScriptWrappable::init(this); | 40 ScriptWrappable::init(this); |
| 55 } | 41 } |
| 56 | 42 |
| 57 DEFINE_NODE_FACTORY(HTMLMetaElement) | 43 DEFINE_NODE_FACTORY(HTMLMetaElement) |
| 58 | 44 |
| 59 static bool isInvalidSeparator(UChar c) | |
| 60 { | |
| 61 return c == ';'; | |
| 62 } | |
| 63 | |
| 64 // Though isspace() considers \t and \v to be whitespace, Win IE doesn't. | |
| 65 static bool isSeparator(UChar c) | |
| 66 { | |
| 67 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == '
,' || c == '\0'; | |
| 68 } | |
| 69 | |
| 70 void HTMLMetaElement::parseContentAttribute(const String& content, KeyValuePairC
allback callback, void* data) | |
| 71 { | |
| 72 bool error = false; | |
| 73 | |
| 74 // Tread lightly in this code -- it was specifically designed to mimic Win I
E's parsing behavior. | |
| 75 unsigned keyBegin, keyEnd; | |
| 76 unsigned valueBegin, valueEnd; | |
| 77 | |
| 78 String buffer = content.lower(); | |
| 79 unsigned length = buffer.length(); | |
| 80 for (unsigned i = 0; i < length; /* no increment here */) { | |
| 81 // skip to first non-separator, but don't skip past the end of the strin
g | |
| 82 while (isSeparator(buffer[i])) { | |
| 83 if (i >= length) | |
| 84 break; | |
| 85 i++; | |
| 86 } | |
| 87 keyBegin = i; | |
| 88 | |
| 89 // skip to first separator | |
| 90 while (!isSeparator(buffer[i])) { | |
| 91 error |= isInvalidSeparator(buffer[i]); | |
| 92 if (i >= length) | |
| 93 break; | |
| 94 i++; | |
| 95 } | |
| 96 keyEnd = i; | |
| 97 | |
| 98 // skip to first '=', but don't skip past a ',' or the end of the string | |
| 99 while (buffer[i] != '=') { | |
| 100 error |= isInvalidSeparator(buffer[i]); | |
| 101 if (buffer[i] == ',' || i >= length) | |
| 102 break; | |
| 103 i++; | |
| 104 } | |
| 105 | |
| 106 // skip to first non-separator, but don't skip past a ',' or the end of
the string | |
| 107 while (isSeparator(buffer[i])) { | |
| 108 if (buffer[i] == ',' || i >= length) | |
| 109 break; | |
| 110 i++; | |
| 111 } | |
| 112 valueBegin = i; | |
| 113 | |
| 114 // skip to first separator | |
| 115 while (!isSeparator(buffer[i])) { | |
| 116 error |= isInvalidSeparator(buffer[i]); | |
| 117 if (i >= length) | |
| 118 break; | |
| 119 i++; | |
| 120 } | |
| 121 valueEnd = i; | |
| 122 | |
| 123 ASSERT_WITH_SECURITY_IMPLICATION(i <= length); | |
| 124 | |
| 125 String keyString = buffer.substring(keyBegin, keyEnd - keyBegin); | |
| 126 String valueString = buffer.substring(valueBegin, valueEnd - valueBegin)
; | |
| 127 (this->*callback)(keyString, valueString, data); | |
| 128 } | |
| 129 if (error) { | |
| 130 String message = "Error parsing a meta element's content: ';' is not a v
alid key-value pair separator. Please use ',' instead."; | |
| 131 document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSour
ce, WarningMessageLevel, message)); | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 static inline float clampLengthValue(float value) | |
| 136 { | |
| 137 // Limits as defined in the css-device-adapt spec. | |
| 138 if (value != ViewportDescription::ValueAuto) | |
| 139 return std::min(float(10000), std::max(value, float(1))); | |
| 140 return value; | |
| 141 } | |
| 142 | |
| 143 static inline float clampScaleValue(float value) | |
| 144 { | |
| 145 // Limits as defined in the css-device-adapt spec. | |
| 146 if (value != ViewportDescription::ValueAuto) | |
| 147 return std::min(float(10), std::max(value, float(0.1))); | |
| 148 return value; | |
| 149 } | |
| 150 | |
| 151 float HTMLMetaElement::parsePositiveNumber(const String& keyString, const String
& valueString, bool* ok) | |
| 152 { | |
| 153 size_t parsedLength; | |
| 154 float value; | |
| 155 if (valueString.is8Bit()) | |
| 156 value = charactersToFloat(valueString.characters8(), valueString.length(
), parsedLength); | |
| 157 else | |
| 158 value = charactersToFloat(valueString.characters16(), valueString.length
(), parsedLength); | |
| 159 if (!parsedLength) { | |
| 160 reportViewportWarning(UnrecognizedViewportArgumentValueError, valueStrin
g, keyString); | |
| 161 if (ok) | |
| 162 *ok = false; | |
| 163 return 0; | |
| 164 } | |
| 165 if (parsedLength < valueString.length()) | |
| 166 reportViewportWarning(TruncatedViewportArgumentValueError, valueString,
keyString); | |
| 167 if (ok) | |
| 168 *ok = true; | |
| 169 return value; | |
| 170 } | |
| 171 | |
| 172 Length HTMLMetaElement::parseViewportValueAsLength(const String& keyString, cons
t String& valueString) | |
| 173 { | |
| 174 // 1) Non-negative number values are translated to px lengths. | |
| 175 // 2) Negative number values are translated to auto. | |
| 176 // 3) device-width and device-height are used as keywords. | |
| 177 // 4) Other keywords and unknown values translate to 0.0. | |
| 178 | |
| 179 unsigned length = valueString.length(); | |
| 180 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13); | |
| 181 SWITCH(characters, length) { | |
| 182 CASE("device-width") { | |
| 183 return Length(DeviceWidth); | |
| 184 } | |
| 185 CASE("device-height") { | |
| 186 return Length(DeviceHeight); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 float value = parsePositiveNumber(keyString, valueString); | |
| 191 | |
| 192 if (value < 0) | |
| 193 return Length(); // auto | |
| 194 | |
| 195 return Length(clampLengthValue(value), Fixed); | |
| 196 } | |
| 197 | |
| 198 float HTMLMetaElement::parseViewportValueAsZoom(const String& keyString, const S
tring& valueString, bool& computedValueMatchesParsedValue) | |
| 199 { | |
| 200 // 1) Non-negative number values are translated to <number> values. | |
| 201 // 2) Negative number values are translated to auto. | |
| 202 // 3) yes is translated to 1.0. | |
| 203 // 4) device-width and device-height are translated to 10.0. | |
| 204 // 5) no and unknown values are translated to 0.0 | |
| 205 | |
| 206 computedValueMatchesParsedValue = false; | |
| 207 unsigned length = valueString.length(); | |
| 208 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13); | |
| 209 SWITCH(characters, length) { | |
| 210 CASE("yes") { | |
| 211 return 1; | |
| 212 } | |
| 213 CASE("no") { | |
| 214 return 0; | |
| 215 } | |
| 216 CASE("device-width") { | |
| 217 return 10; | |
| 218 } | |
| 219 CASE("device-height") { | |
| 220 return 10; | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 float value = parsePositiveNumber(keyString, valueString); | |
| 225 | |
| 226 if (value < 0) | |
| 227 return ViewportDescription::ValueAuto; | |
| 228 | |
| 229 if (value > 10.0) | |
| 230 reportViewportWarning(MaximumScaleTooLargeError, String(), String()); | |
| 231 | |
| 232 float clampedValue = clampScaleValue(value); | |
| 233 if (clampedValue == value) | |
| 234 computedValueMatchesParsedValue = true; | |
| 235 | |
| 236 return clampedValue; | |
| 237 } | |
| 238 | |
| 239 bool HTMLMetaElement::parseViewportValueAsUserZoom(const String& keyString, cons
t String& valueString, bool& computedValueMatchesParsedValue) | |
| 240 { | |
| 241 // yes and no are used as keywords. | |
| 242 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to
yes. | |
| 243 // Numbers in the range <-1, 1>, and unknown values, are mapped to no. | |
| 244 | |
| 245 computedValueMatchesParsedValue = false; | |
| 246 unsigned length = valueString.length(); | |
| 247 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13); | |
| 248 SWITCH(characters, length) { | |
| 249 CASE("yes") { | |
| 250 computedValueMatchesParsedValue = true; | |
| 251 return true; | |
| 252 } | |
| 253 CASE("no") { | |
| 254 computedValueMatchesParsedValue = true; | |
| 255 return false; | |
| 256 } | |
| 257 CASE("device-width") { | |
| 258 return true; | |
| 259 } | |
| 260 CASE("device-height") { | |
| 261 return true; | |
| 262 } | |
| 263 } | |
| 264 | |
| 265 float value = parsePositiveNumber(keyString, valueString); | |
| 266 if (fabs(value) < 1) | |
| 267 return false; | |
| 268 | |
| 269 return true; | |
| 270 } | |
| 271 | |
| 272 float HTMLMetaElement::parseViewportValueAsDPI(const String& keyString, const St
ring& valueString) | |
| 273 { | |
| 274 unsigned length = valueString.length(); | |
| 275 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10); | |
| 276 SWITCH(characters, length) { | |
| 277 CASE("device-dpi") { | |
| 278 return ViewportDescription::ValueDeviceDPI; | |
| 279 } | |
| 280 CASE("low-dpi") { | |
| 281 return ViewportDescription::ValueLowDPI; | |
| 282 } | |
| 283 CASE("medium-dpi") { | |
| 284 return ViewportDescription::ValueMediumDPI; | |
| 285 } | |
| 286 CASE("high-dpi") { | |
| 287 return ViewportDescription::ValueHighDPI; | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 bool ok; | |
| 292 float value = parsePositiveNumber(keyString, valueString, &ok); | |
| 293 if (!ok || value < 70 || value > 400) | |
| 294 return ViewportDescription::ValueAuto; | |
| 295 | |
| 296 return value; | |
| 297 } | |
| 298 | |
| 299 void HTMLMetaElement::processViewportKeyValuePair(const String& keyString, const
String& valueString, void* data) | |
| 300 { | |
| 301 ViewportDescription* description = static_cast<ViewportDescription*>(data); | |
| 302 | |
| 303 unsigned length = keyString.length(); | |
| 304 | |
| 305 DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17); | |
| 306 SWITCH(characters, length) { | |
| 307 CASE("width") { | |
| 308 const Length& width = parseViewportValueAsLength(keyString, valueStr
ing); | |
| 309 if (width.isAuto()) | |
| 310 return; | |
| 311 description->minWidth = Length(ExtendToZoom); | |
| 312 description->maxWidth = width; | |
| 313 return; | |
| 314 } | |
| 315 CASE("height") { | |
| 316 const Length& height = parseViewportValueAsLength(keyString, valueSt
ring); | |
| 317 if (height.isAuto()) | |
| 318 return; | |
| 319 description->minHeight = Length(ExtendToZoom); | |
| 320 description->maxHeight = height; | |
| 321 return; | |
| 322 } | |
| 323 CASE("initial-scale") { | |
| 324 description->zoom = parseViewportValueAsZoom(keyString, valueString,
description->zoomIsExplicit); | |
| 325 return; | |
| 326 } | |
| 327 CASE("minimum-scale") { | |
| 328 description->minZoom = parseViewportValueAsZoom(keyString, valueStri
ng, description->minZoomIsExplicit); | |
| 329 return; | |
| 330 } | |
| 331 CASE("maximum-scale") { | |
| 332 description->maxZoom = parseViewportValueAsZoom(keyString, valueStri
ng, description->maxZoomIsExplicit); | |
| 333 return; | |
| 334 } | |
| 335 CASE("user-scalable") { | |
| 336 description->userZoom = parseViewportValueAsUserZoom(keyString, valu
eString, description->userZoomIsExplicit); | |
| 337 return; | |
| 338 } | |
| 339 CASE("target-densitydpi") { | |
| 340 description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(ke
yString, valueString); | |
| 341 reportViewportWarning(TargetDensityDpiUnsupported, String(), String(
)); | |
| 342 return; | |
| 343 } | |
| 344 CASE("minimal-ui") { | |
| 345 // Ignore vendor-specific argument. | |
| 346 return; | |
| 347 } | |
| 348 } | |
| 349 reportViewportWarning(UnrecognizedViewportArgumentKeyError, keyString, Strin
g()); | |
| 350 } | |
| 351 | |
| 352 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode) | |
| 353 { | |
| 354 static const char* const errors[] = { | |
| 355 "The key \"%replacement1\" is not recognized and ignored.", | |
| 356 "The value \"%replacement1\" for key \"%replacement2\" is invalid, and h
as been ignored.", | |
| 357 "The value \"%replacement1\" for key \"%replacement2\" was truncated to
its numeric prefix.", | |
| 358 "The value for key \"maximum-scale\" is out of bounds and the value has
been clamped.", | |
| 359 "The key \"target-densitydpi\" is not supported.", | |
| 360 }; | |
| 361 | |
| 362 return errors[errorCode]; | |
| 363 } | |
| 364 | |
| 365 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode) | |
| 366 { | |
| 367 switch (errorCode) { | |
| 368 case TruncatedViewportArgumentValueError: | |
| 369 case TargetDensityDpiUnsupported: | |
| 370 case UnrecognizedViewportArgumentKeyError: | |
| 371 case UnrecognizedViewportArgumentValueError: | |
| 372 case MaximumScaleTooLargeError: | |
| 373 return WarningMessageLevel; | |
| 374 } | |
| 375 | |
| 376 ASSERT_NOT_REACHED(); | |
| 377 return ErrorMessageLevel; | |
| 378 } | |
| 379 | |
| 380 void HTMLMetaElement::reportViewportWarning(ViewportErrorCode errorCode, const S
tring& replacement1, const String& replacement2) | |
| 381 { | |
| 382 if (!document().frame()) | |
| 383 return; | |
| 384 | |
| 385 String message = viewportErrorMessageTemplate(errorCode); | |
| 386 if (!replacement1.isNull()) | |
| 387 message.replace("%replacement1", replacement1); | |
| 388 if (!replacement2.isNull()) | |
| 389 message.replace("%replacement2", replacement2); | |
| 390 | |
| 391 // FIXME: This message should be moved off the console once a solution to ht
tps://bugs.webkit.org/show_bug.cgi?id=103274 exists. | |
| 392 document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource,
viewportErrorMessageLevel(errorCode), message)); | |
| 393 } | |
| 394 | |
| 395 void HTMLMetaElement::processViewportContentAttribute(const String& content, Vie
wportDescription::Type origin) | |
| 396 { | |
| 397 ASSERT(!content.isNull()); | |
| 398 | |
| 399 if (!document().shouldOverrideLegacyDescription(origin)) | |
| 400 return; | |
| 401 | |
| 402 ViewportDescription descriptionFromLegacyTag(origin); | |
| 403 | |
| 404 parseContentAttribute(content, &HTMLMetaElement::processViewportKeyValuePair
, (void*)&descriptionFromLegacyTag); | |
| 405 | |
| 406 if (descriptionFromLegacyTag.minZoom == ViewportDescription::ValueAuto) | |
| 407 descriptionFromLegacyTag.minZoom = 0.25; | |
| 408 | |
| 409 if (descriptionFromLegacyTag.maxZoom == ViewportDescription::ValueAuto) { | |
| 410 descriptionFromLegacyTag.maxZoom = 5; | |
| 411 descriptionFromLegacyTag.minZoom = std::min(descriptionFromLegacyTag.min
Zoom, float(5)); | |
| 412 } | |
| 413 | |
| 414 document().setViewportDescription(descriptionFromLegacyTag); | |
| 415 } | |
| 416 | |
| 417 | |
| 418 void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicStri
ng& value) | 45 void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicStri
ng& value) |
| 419 { | 46 { |
| 420 if (name == HTMLNames::http_equivAttr || name == HTMLNames::contentAttr) { | 47 if (name == HTMLNames::http_equivAttr || name == HTMLNames::contentAttr) { |
| 421 process(); | 48 process(); |
| 422 return; | 49 return; |
| 423 } | 50 } |
| 424 | 51 |
| 425 if (name != HTMLNames::nameAttr) | 52 if (name != HTMLNames::nameAttr) |
| 426 HTMLElement::parseAttribute(name, value); | 53 HTMLElement::parseAttribute(name, value); |
| 427 } | 54 } |
| (...skipping 13 matching lines...) Expand all Loading... |
| 441 { | 68 { |
| 442 if (!inDocument()) | 69 if (!inDocument()) |
| 443 return; | 70 return; |
| 444 | 71 |
| 445 // All below situations require a content attribute (which can be the empty
string). | 72 // All below situations require a content attribute (which can be the empty
string). |
| 446 const AtomicString& contentValue = getAttribute(HTMLNames::contentAttr); | 73 const AtomicString& contentValue = getAttribute(HTMLNames::contentAttr); |
| 447 if (contentValue.isNull()) | 74 if (contentValue.isNull()) |
| 448 return; | 75 return; |
| 449 | 76 |
| 450 const AtomicString& nameValue = getAttribute(HTMLNames::nameAttr); | 77 const AtomicString& nameValue = getAttribute(HTMLNames::nameAttr); |
| 451 if (!nameValue.isEmpty()) { | 78 if (!nameValue.isEmpty() && equalIgnoringCase(nameValue, "referrer")) |
| 452 if (equalIgnoringCase(nameValue, "viewport")) | 79 document().processReferrerPolicy(contentValue); |
| 453 processViewportContentAttribute(contentValue, ViewportDescription::V
iewportMeta); | |
| 454 else if (equalIgnoringCase(nameValue, "referrer")) | |
| 455 document().processReferrerPolicy(contentValue); | |
| 456 else if (equalIgnoringCase(nameValue, "handheldfriendly") && equalIgnori
ngCase(contentValue, "true")) | |
| 457 processViewportContentAttribute("width=device-width", ViewportDescri
ption::HandheldFriendlyMeta); | |
| 458 else if (equalIgnoringCase(nameValue, "mobileoptimized")) | |
| 459 processViewportContentAttribute("width=device-width, initial-scale=1
", ViewportDescription::MobileOptimizedMeta); | |
| 460 } | |
| 461 | 80 |
| 462 // Get the document to process the tag, but only if we're actually part of D
OM | 81 // Get the document to process the tag, but only if we're actually part of D
OM |
| 463 // tree (changing a meta tag while it's not in the tree shouldn't have any e
ffect | 82 // tree (changing a meta tag while it's not in the tree shouldn't have any e
ffect |
| 464 // on the document). | 83 // on the document). |
| 465 | 84 |
| 466 const AtomicString& httpEquivValue = getAttribute(HTMLNames::http_equivAttr)
; | 85 const AtomicString& httpEquivValue = getAttribute(HTMLNames::http_equivAttr)
; |
| 467 if (!httpEquivValue.isEmpty()) | 86 if (!httpEquivValue.isEmpty()) |
| 468 document().processHttpEquiv(httpEquivValue, contentValue, false); | 87 document().processHttpEquiv(httpEquivValue, contentValue, false); |
| 469 } | 88 } |
| 470 | 89 |
| 471 const AtomicString& HTMLMetaElement::content() const | 90 const AtomicString& HTMLMetaElement::content() const |
| 472 { | 91 { |
| 473 return getAttribute(HTMLNames::contentAttr); | 92 return getAttribute(HTMLNames::contentAttr); |
| 474 } | 93 } |
| 475 | 94 |
| 476 const AtomicString& HTMLMetaElement::httpEquiv() const | 95 const AtomicString& HTMLMetaElement::httpEquiv() const |
| 477 { | 96 { |
| 478 return getAttribute(HTMLNames::http_equivAttr); | 97 return getAttribute(HTMLNames::http_equivAttr); |
| 479 } | 98 } |
| 480 | 99 |
| 481 const AtomicString& HTMLMetaElement::name() const | 100 const AtomicString& HTMLMetaElement::name() const |
| 482 { | 101 { |
| 483 return getAttribute(HTMLNames::nameAttr); | 102 return getAttribute(HTMLNames::nameAttr); |
| 484 } | 103 } |
| 485 | 104 |
| 486 } | 105 } |
| OLD | NEW |