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 "core/HTMLNames.h"
27 #include "core/dom/Document.h"
28 #include "core/dom/ElementTraversal.h"
29 #include "core/frame/LocalFrame.h"
30 #include "core/frame/Settings.h"
31 #include "core/html/HTMLHeadElement.h"
32 #include "core/inspector/ConsoleMessage.h"
33 #include "core/loader/FrameLoaderClient.h"
34 #include "platform/RuntimeEnabledFeatures.h"
38 #define DEFINE_ARRAY_FOR_MATCHING(name, source, maxMatchLength) \
40 const unsigned uMaxMatchLength = maxMatchLength; \
41 UChar characterBuffer[uMaxMatchLength]; \
42 if (!source.is8Bit()) { \
43 name = source.characters16(); \
45 unsigned bufferLength = std::min(uMaxMatchLength, source.length()); \
46 const LChar* characters8 = source.characters8(); \
47 for (unsigned i = 0; i < bufferLength; ++i) \
48 characterBuffer[i] = characters8[i]; \
49 name = characterBuffer; \
52 using namespace HTMLNames;
54 inline HTMLMetaElement::HTMLMetaElement(Document& document)
55 : HTMLElement(metaTag, document)
59 DEFINE_NODE_FACTORY(HTMLMetaElement)
61 static bool isInvalidSeparator(UChar c)
66 // Though isspace() considers \t and \v to be whitespace, Win IE doesn't.
67 static bool isSeparator(UChar c)
69 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0';
72 void HTMLMetaElement::parseContentAttribute(const String& content, KeyValuePairCallback callback, void* data)
76 // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior.
77 unsigned keyBegin, keyEnd;
78 unsigned valueBegin, valueEnd;
80 String buffer = content.lower();
81 unsigned length = buffer.length();
82 for (unsigned i = 0; i < length; /* no increment here */) {
83 // skip to first non-separator, but don't skip past the end of the string
84 while (isSeparator(buffer[i])) {
91 // skip to first separator
92 while (!isSeparator(buffer[i])) {
93 error |= isInvalidSeparator(buffer[i]);
100 // skip to first '=', but don't skip past a ',' or the end of the string
101 while (buffer[i] != '=') {
102 error |= isInvalidSeparator(buffer[i]);
103 if (buffer[i] == ',' || i >= length)
108 // skip to first non-separator, but don't skip past a ',' or the end of the string
109 while (isSeparator(buffer[i])) {
110 if (buffer[i] == ',' || i >= length)
116 // skip to first separator
117 while (!isSeparator(buffer[i])) {
118 error |= isInvalidSeparator(buffer[i]);
125 ASSERT_WITH_SECURITY_IMPLICATION(i <= length);
127 String keyString = buffer.substring(keyBegin, keyEnd - keyBegin);
128 String valueString = buffer.substring(valueBegin, valueEnd - valueBegin);
129 (this->*callback)(keyString, valueString, data);
132 String message = "Error parsing a meta element's content: ';' is not a valid key-value pair separator. Please use ',' instead.";
133 document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource, WarningMessageLevel, message));
137 static inline float clampLengthValue(float value)
139 // Limits as defined in the css-device-adapt spec.
140 if (value != ViewportDescription::ValueAuto)
141 return std::min(float(10000), std::max(value, float(1)));
145 static inline float clampScaleValue(float value)
147 // Limits as defined in the css-device-adapt spec.
148 if (value != ViewportDescription::ValueAuto)
149 return std::min(float(10), std::max(value, float(0.1)));
153 float HTMLMetaElement::parsePositiveNumber(const String& keyString, const String& valueString, bool* ok)
157 if (valueString.is8Bit())
158 value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength);
160 value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength);
162 reportViewportWarning(UnrecognizedViewportArgumentValueError, valueString, keyString);
167 if (parsedLength < valueString.length())
168 reportViewportWarning(TruncatedViewportArgumentValueError, valueString, keyString);
174 Length HTMLMetaElement::parseViewportValueAsLength(const String& keyString, const String& valueString)
176 // 1) Non-negative number values are translated to px lengths.
177 // 2) Negative number values are translated to auto.
178 // 3) device-width and device-height are used as keywords.
179 // 4) Other keywords and unknown values translate to 0.0.
181 unsigned length = valueString.length();
182 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
183 SWITCH(characters, length) {
184 CASE("device-width") {
185 return Length(DeviceWidth);
187 CASE("device-height") {
188 return Length(DeviceHeight);
192 float value = parsePositiveNumber(keyString, valueString);
195 return Length(); // auto
197 return Length(clampLengthValue(value), Fixed);
200 float HTMLMetaElement::parseViewportValueAsZoom(const String& keyString, const String& valueString, bool& computedValueMatchesParsedValue)
202 // 1) Non-negative number values are translated to <number> values.
203 // 2) Negative number values are translated to auto.
204 // 3) yes is translated to 1.0.
205 // 4) device-width and device-height are translated to 10.0.
206 // 5) no and unknown values are translated to 0.0
208 computedValueMatchesParsedValue = false;
209 unsigned length = valueString.length();
210 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
211 SWITCH(characters, length) {
218 CASE("device-width") {
221 CASE("device-height") {
226 float value = parsePositiveNumber(keyString, valueString);
229 return ViewportDescription::ValueAuto;
232 reportViewportWarning(MaximumScaleTooLargeError, String(), String());
234 if (!value && document().settings() && document().settings()->viewportMetaZeroValuesQuirk())
235 return ViewportDescription::ValueAuto;
237 float clampedValue = clampScaleValue(value);
238 if (clampedValue == value)
239 computedValueMatchesParsedValue = true;
244 bool HTMLMetaElement::parseViewportValueAsUserZoom(const String& keyString, const String& valueString, bool& computedValueMatchesParsedValue)
246 // yes and no are used as keywords.
247 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
248 // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
250 computedValueMatchesParsedValue = false;
251 unsigned length = valueString.length();
252 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
253 SWITCH(characters, length) {
255 computedValueMatchesParsedValue = true;
259 computedValueMatchesParsedValue = true;
262 CASE("device-width") {
265 CASE("device-height") {
270 float value = parsePositiveNumber(keyString, valueString);
277 float HTMLMetaElement::parseViewportValueAsDPI(const String& keyString, const String& valueString)
279 unsigned length = valueString.length();
280 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10);
281 SWITCH(characters, length) {
283 return ViewportDescription::ValueDeviceDPI;
286 return ViewportDescription::ValueLowDPI;
289 return ViewportDescription::ValueMediumDPI;
292 return ViewportDescription::ValueHighDPI;
297 float value = parsePositiveNumber(keyString, valueString, &ok);
298 if (!ok || value < 70 || value > 400)
299 return ViewportDescription::ValueAuto;
304 void HTMLMetaElement::processViewportKeyValuePair(const String& keyString, const String& valueString, void* data)
306 ViewportDescription* description = static_cast<ViewportDescription*>(data);
308 unsigned length = keyString.length();
310 DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17);
311 SWITCH(characters, length) {
313 const Length& width = parseViewportValueAsLength(keyString, valueString);
316 description->minWidth = Length(ExtendToZoom);
317 description->maxWidth = width;
321 const Length& height = parseViewportValueAsLength(keyString, valueString);
324 description->minHeight = Length(ExtendToZoom);
325 description->maxHeight = height;
328 CASE("initial-scale") {
329 description->zoom = parseViewportValueAsZoom(keyString, valueString, description->zoomIsExplicit);
332 CASE("minimum-scale") {
333 description->minZoom = parseViewportValueAsZoom(keyString, valueString, description->minZoomIsExplicit);
336 CASE("maximum-scale") {
337 description->maxZoom = parseViewportValueAsZoom(keyString, valueString, description->maxZoomIsExplicit);
340 CASE("user-scalable") {
341 description->userZoom = parseViewportValueAsUserZoom(keyString, valueString, description->userZoomIsExplicit);
344 CASE("target-densitydpi") {
345 description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(keyString, valueString);
346 reportViewportWarning(TargetDensityDpiUnsupported, String(), String());
350 // Ignore vendor-specific argument.
354 reportViewportWarning(UnrecognizedViewportArgumentKeyError, keyString, String());
357 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
359 static const char* const errors[] = {
360 "The key \"%replacement1\" is not recognized and ignored.",
361 "The value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.",
362 "The value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
363 "The value for key \"maximum-scale\" is out of bounds and the value has been clamped.",
364 "The key \"target-densitydpi\" is not supported.",
367 return errors[errorCode];
370 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
373 case TruncatedViewportArgumentValueError:
374 case TargetDensityDpiUnsupported:
375 case UnrecognizedViewportArgumentKeyError:
376 case UnrecognizedViewportArgumentValueError:
377 case MaximumScaleTooLargeError:
378 return WarningMessageLevel;
381 ASSERT_NOT_REACHED();
382 return ErrorMessageLevel;
385 void HTMLMetaElement::reportViewportWarning(ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
387 if (!document().frame())
390 String message = viewportErrorMessageTemplate(errorCode);
391 if (!replacement1.isNull())
392 message.replace("%replacement1", replacement1);
393 if (!replacement2.isNull())
394 message.replace("%replacement2", replacement2);
396 // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
397 document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource, viewportErrorMessageLevel(errorCode), message));
400 void HTMLMetaElement::processViewportContentAttribute(const String& content, ViewportDescription::Type origin)
402 ASSERT(!content.isNull());
404 if (!document().shouldOverrideLegacyDescription(origin))
407 ViewportDescription descriptionFromLegacyTag(origin);
408 if (document().shouldMergeWithLegacyDescription(origin))
409 descriptionFromLegacyTag = document().viewportDescription();
411 parseContentAttribute(content, &HTMLMetaElement::processViewportKeyValuePair, (void*)&descriptionFromLegacyTag);
413 if (descriptionFromLegacyTag.minZoom == ViewportDescription::ValueAuto)
414 descriptionFromLegacyTag.minZoom = 0.25;
416 if (descriptionFromLegacyTag.maxZoom == ViewportDescription::ValueAuto) {
417 descriptionFromLegacyTag.maxZoom = 5;
418 descriptionFromLegacyTag.minZoom = std::min(descriptionFromLegacyTag.minZoom, float(5));
421 document().setViewportDescription(descriptionFromLegacyTag);
425 void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
427 if (name == http_equivAttr || name == contentAttr) {
432 if (name != nameAttr)
433 HTMLElement::parseAttribute(name, value);
436 Node::InsertionNotificationRequest HTMLMetaElement::insertedInto(ContainerNode* insertionPoint)
438 HTMLElement::insertedInto(insertionPoint);
439 return InsertionShouldCallDidNotifySubtreeInsertions;
442 void HTMLMetaElement::didNotifySubtreeInsertionsToDocument()
447 static bool inDocumentHead(HTMLMetaElement* element)
449 if (!element->inDocument())
452 return Traversal<HTMLHeadElement>::firstAncestor(*element);
455 void HTMLMetaElement::process()
460 // All below situations require a content attribute (which can be the empty string).
461 const AtomicString& contentValue = fastGetAttribute(contentAttr);
462 if (contentValue.isNull())
465 const AtomicString& nameValue = fastGetAttribute(nameAttr);
466 if (!nameValue.isEmpty()) {
467 if (equalIgnoringCase(nameValue, "viewport"))
468 processViewportContentAttribute(contentValue, ViewportDescription::ViewportMeta);
469 else if (equalIgnoringCase(nameValue, "referrer"))
470 document().processReferrerPolicy(contentValue);
471 else if (equalIgnoringCase(nameValue, "handheldfriendly") && equalIgnoringCase(contentValue, "true"))
472 processViewportContentAttribute("width=device-width", ViewportDescription::HandheldFriendlyMeta);
473 else if (equalIgnoringCase(nameValue, "mobileoptimized"))
474 processViewportContentAttribute("width=device-width, initial-scale=1", ViewportDescription::MobileOptimizedMeta);
475 else if (equalIgnoringCase(nameValue, "theme-color") && document().frame())
476 document().frame()->loader().client()->dispatchDidChangeThemeColor();
479 // Get the document to process the tag, but only if we're actually part of DOM
480 // tree (changing a meta tag while it's not in the tree shouldn't have any effect
483 const AtomicString& httpEquivValue = fastGetAttribute(http_equivAttr);
484 if (!httpEquivValue.isEmpty())
485 document().processHttpEquiv(httpEquivValue, contentValue, inDocumentHead(this));
488 const AtomicString& HTMLMetaElement::content() const
490 return getAttribute(contentAttr);
493 const AtomicString& HTMLMetaElement::httpEquiv() const
495 return getAttribute(http_equivAttr);
498 const AtomicString& HTMLMetaElement::name() const
500 return getNameAttribute();