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 * Copyright (C) 2003, 2010 Apple Inc. All rights reserved.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
24 #include "core/html/HTMLMetaElement.h"
26 #include "HTMLNames.h"
27 #include "core/dom/Document.h"
28 #include "core/frame/Settings.h"
32 #define DEFINE_ARRAY_FOR_MATCHING(name, source, maxMatchLength) \
34 const unsigned uMaxMatchLength = maxMatchLength; \
35 UChar characterBuffer[uMaxMatchLength]; \
36 if (!source.is8Bit()) { \
37 name = source.characters16(); \
39 unsigned bufferLength = std::min(uMaxMatchLength, source.length()); \
40 const LChar* characters8 = source.characters8(); \
41 for (unsigned i = 0; i < bufferLength; ++i) \
42 characterBuffer[i] = characters8[i]; \
43 name = characterBuffer; \
46 using namespace HTMLNames;
48 inline HTMLMetaElement::HTMLMetaElement(Document& document)
49 : HTMLElement(metaTag, document)
51 ScriptWrappable::init(this);
54 PassRefPtr<HTMLMetaElement> HTMLMetaElement::create(Document& document)
56 return adoptRef(new HTMLMetaElement(document));
59 static bool isInvalidSeparator(UChar c)
64 // Though isspace() considers \t and \v to be whitespace, Win IE doesn't.
65 static bool isSeparator(UChar c)
67 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0';
70 void HTMLMetaElement::parseContentAttribute(const String& content, KeyValuePairCallback callback, void* data)
74 // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior.
76 int valueBegin, valueEnd;
79 int length = content.length();
80 String buffer = content.lower();
82 // skip to first non-separator, but don't skip past the end of the string
83 while (isSeparator(buffer[i])) {
90 // skip to first separator
91 while (!isSeparator(buffer[i])) {
92 error |= isInvalidSeparator(buffer[i]);
97 // skip to first '=', but don't skip past a ',' or the end of the string
98 while (buffer[i] != '=') {
99 error |= isInvalidSeparator(buffer[i]);
100 if (buffer[i] == ',' || i >= length)
105 // skip to first non-separator, but don't skip past a ',' or the end of the string
106 while (isSeparator(buffer[i])) {
107 if (buffer[i] == ',' || i >= length)
113 // skip to first separator
114 while (!isSeparator(buffer[i])) {
115 error |= isInvalidSeparator(buffer[i]);
120 ASSERT_WITH_SECURITY_IMPLICATION(i <= length);
122 String keyString = buffer.substring(keyBegin, keyEnd - keyBegin);
123 String valueString = buffer.substring(valueBegin, valueEnd - valueBegin);
124 (this->*callback)(keyString, valueString, data);
127 String message = "Error parsing a meta element's content: ';' is not a valid key-value pair separator. Please use ',' instead.";
128 document().addConsoleMessage(RenderingMessageSource, WarningMessageLevel, message);
132 static inline float clampLengthValue(float value)
134 // Limits as defined in the css-device-adapt spec.
135 if (value != ViewportDescription::ValueAuto)
136 return std::min(float(10000), std::max(value, float(1)));
140 static inline float clampScaleValue(float value)
142 // Limits as defined in the css-device-adapt spec.
143 if (value != ViewportDescription::ValueAuto)
144 return std::min(float(10), std::max(value, float(0.1)));
148 float HTMLMetaElement::parsePositiveNumber(const String& keyString, const String& valueString, bool* ok)
152 if (valueString.is8Bit())
153 value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength);
155 value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength);
157 reportViewportWarning(UnrecognizedViewportArgumentValueError, valueString, keyString);
162 if (parsedLength < valueString.length())
163 reportViewportWarning(TruncatedViewportArgumentValueError, valueString, keyString);
169 Length HTMLMetaElement::parseViewportValueAsLength(const String& keyString, const String& valueString)
171 // 1) Non-negative number values are translated to px lengths.
172 // 2) Negative number values are translated to auto.
173 // 3) device-width and device-height are used as keywords.
174 // 4) Other keywords and unknown values translate to 0.0.
176 unsigned length = valueString.length();
177 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
178 SWITCH(characters, length) {
179 CASE("device-width") {
180 return Length(DeviceWidth);
182 CASE("device-height") {
183 return Length(DeviceHeight);
187 float value = parsePositiveNumber(keyString, valueString);
190 return Length(); // auto
192 return Length(clampLengthValue(value), Fixed);
195 float HTMLMetaElement::parseViewportValueAsZoom(const String& keyString, const String& valueString)
197 // 1) Non-negative number values are translated to <number> values.
198 // 2) Negative number values are translated to auto.
199 // 3) yes is translated to 1.0.
200 // 4) device-width and device-height are translated to 10.0.
201 // 5) no and unknown values are translated to 0.0
203 unsigned length = valueString.length();
204 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
205 SWITCH(characters, length) {
212 CASE("device-width") {
215 CASE("device-height") {
220 float value = parsePositiveNumber(keyString, valueString);
223 return ViewportDescription::ValueAuto;
226 reportViewportWarning(MaximumScaleTooLargeError, String(), String());
228 if (!value && document().settings() && document().settings()->viewportMetaZeroValuesQuirk())
229 return ViewportDescription::ValueAuto;
231 return clampScaleValue(value);
234 float HTMLMetaElement::parseViewportValueAsUserZoom(const String& keyString, const String& valueString)
236 // yes and no are used as keywords.
237 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
238 // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
240 unsigned length = valueString.length();
241 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
242 SWITCH(characters, length) {
249 CASE("device-width") {
252 CASE("device-height") {
257 float value = parsePositiveNumber(keyString, valueString);
264 float HTMLMetaElement::parseViewportValueAsDPI(const String& keyString, const String& valueString)
266 unsigned length = valueString.length();
267 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10);
268 SWITCH(characters, length) {
270 return ViewportDescription::ValueDeviceDPI;
273 return ViewportDescription::ValueLowDPI;
276 return ViewportDescription::ValueMediumDPI;
279 return ViewportDescription::ValueHighDPI;
284 float value = parsePositiveNumber(keyString, valueString, &ok);
285 if (!ok || value < 70 || value > 400)
286 return ViewportDescription::ValueAuto;
291 void HTMLMetaElement::processViewportKeyValuePair(const String& keyString, const String& valueString, void* data)
293 ViewportDescription* description = static_cast<ViewportDescription*>(data);
295 unsigned length = keyString.length();
297 DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17);
298 SWITCH(characters, length) {
300 const Length& width = parseViewportValueAsLength(keyString, valueString);
303 description->minWidth = Length(ExtendToZoom);
304 description->maxWidth = width;
308 const Length& height = parseViewportValueAsLength(keyString, valueString);
311 description->minHeight = Length(ExtendToZoom);
312 description->maxHeight = height;
315 CASE("initial-scale") {
316 description->zoom = parseViewportValueAsZoom(keyString, valueString);
319 CASE("minimum-scale") {
320 description->minZoom = parseViewportValueAsZoom(keyString, valueString);
323 CASE("maximum-scale") {
324 description->maxZoom = parseViewportValueAsZoom(keyString, valueString);
327 CASE("user-scalable") {
328 description->userZoom = parseViewportValueAsUserZoom(keyString, valueString);
331 CASE("target-densitydpi") {
332 description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(keyString, valueString);
333 reportViewportWarning(TargetDensityDpiUnsupported, String(), String());
337 // Ignore vendor-specific argument.
341 reportViewportWarning(UnrecognizedViewportArgumentKeyError, keyString, String());
344 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
346 static const char* const errors[] = {
347 "The key \"%replacement1\" is not recognized and ignored.",
348 "The value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.",
349 "The value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
350 "The value for key \"maximum-scale\" is out of bounds and the value has been clamped.",
351 "The key \"target-densitydpi\" is not supported.",
354 return errors[errorCode];
357 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
360 case TruncatedViewportArgumentValueError:
361 case TargetDensityDpiUnsupported:
362 case UnrecognizedViewportArgumentKeyError:
363 case UnrecognizedViewportArgumentValueError:
364 case MaximumScaleTooLargeError:
365 return WarningMessageLevel;
368 ASSERT_NOT_REACHED();
369 return ErrorMessageLevel;
372 void HTMLMetaElement::reportViewportWarning(ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
374 if (!document().frame())
377 String message = viewportErrorMessageTemplate(errorCode);
378 if (!replacement1.isNull())
379 message.replace("%replacement1", replacement1);
380 if (!replacement2.isNull())
381 message.replace("%replacement2", replacement2);
383 // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
384 document().addConsoleMessage(RenderingMessageSource, viewportErrorMessageLevel(errorCode), message);
387 void HTMLMetaElement::processViewportContentAttribute(const String& content, ViewportDescription::Type origin)
389 ASSERT(!content.isNull());
391 if (!document().settings())
394 if (!document().shouldOverrideLegacyDescription(origin))
397 ViewportDescription descriptionFromLegacyTag(origin);
398 if (document().shouldMergeWithLegacyDescription(origin))
399 descriptionFromLegacyTag = document().viewportDescription();
401 parseContentAttribute(content, &HTMLMetaElement::processViewportKeyValuePair, (void*)&descriptionFromLegacyTag);
403 if (descriptionFromLegacyTag.minZoom == ViewportDescription::ValueAuto)
404 descriptionFromLegacyTag.minZoom = 0.25;
406 if (descriptionFromLegacyTag.maxZoom == ViewportDescription::ValueAuto) {
407 descriptionFromLegacyTag.maxZoom = 5;
408 descriptionFromLegacyTag.minZoom = std::min(descriptionFromLegacyTag.minZoom, float(5));
411 const Settings* settings = document().settings();
413 if (descriptionFromLegacyTag.maxWidth.isAuto()) {
414 if (descriptionFromLegacyTag.zoom == ViewportDescription::ValueAuto) {
415 descriptionFromLegacyTag.minWidth = Length(ExtendToZoom);
416 descriptionFromLegacyTag.maxWidth = Length(settings->layoutFallbackWidth(), Fixed);
417 } else if (descriptionFromLegacyTag.maxHeight.isAuto()) {
418 descriptionFromLegacyTag.minWidth = Length(ExtendToZoom);
419 descriptionFromLegacyTag.maxWidth = Length(ExtendToZoom);
423 document().setViewportDescription(descriptionFromLegacyTag);
427 void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
429 if (name == http_equivAttr || name == contentAttr) {
434 if (name != nameAttr)
435 HTMLElement::parseAttribute(name, value);
438 Node::InsertionNotificationRequest HTMLMetaElement::insertedInto(ContainerNode* insertionPoint)
440 HTMLElement::insertedInto(insertionPoint);
441 if (insertionPoint->inDocument())
443 return InsertionDone;
446 static bool inDocumentHead(HTMLMetaElement* element)
448 if (!element->inDocument())
451 for (Element* current = element; current; current = current->parentElement()) {
452 if (current->hasTagName(HTMLNames::headTag))
458 void HTMLMetaElement::process()
463 // All below situations require a content attribute (which can be the empty string).
464 const AtomicString& contentValue = fastGetAttribute(contentAttr);
465 if (contentValue.isNull())
468 const AtomicString& nameValue = fastGetAttribute(nameAttr);
469 if (!nameValue.isEmpty()) {
470 if (equalIgnoringCase(nameValue, "viewport"))
471 processViewportContentAttribute(contentValue, ViewportDescription::ViewportMeta);
472 else if (equalIgnoringCase(nameValue, "referrer"))
473 document().processReferrerPolicy(contentValue);
474 else if (equalIgnoringCase(nameValue, "handheldfriendly") && equalIgnoringCase(contentValue, "true"))
475 processViewportContentAttribute("width=device-width", ViewportDescription::HandheldFriendlyMeta);
476 else if (equalIgnoringCase(nameValue, "mobileoptimized"))
477 processViewportContentAttribute("width=device-width, initial-scale=1", ViewportDescription::MobileOptimizedMeta);
480 // Get the document to process the tag, but only if we're actually part of DOM
481 // tree (changing a meta tag while it's not in the tree shouldn't have any effect
484 const AtomicString& httpEquivValue = fastGetAttribute(http_equivAttr);
485 if (!httpEquivValue.isEmpty())
486 document().processHttpEquiv(httpEquivValue, contentValue, inDocumentHead(this));
489 const AtomicString& HTMLMetaElement::content() const
491 return getAttribute(contentAttr);
494 const AtomicString& HTMLMetaElement::httpEquiv() const
496 return getAttribute(http_equivAttr);
499 const AtomicString& HTMLMetaElement::name() const
501 return getNameAttribute();