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/frame/LocalFrame.h"
29 #include "core/frame/Settings.h"
30 #include "core/loader/FrameLoaderClient.h"
31 #include "platform/RuntimeEnabledFeatures.h"
35 #define DEFINE_ARRAY_FOR_MATCHING(name, source, maxMatchLength) \
37 const unsigned uMaxMatchLength = maxMatchLength; \
38 UChar characterBuffer[uMaxMatchLength]; \
39 if (!source.is8Bit()) { \
40 name = source.characters16(); \
42 unsigned bufferLength = std::min(uMaxMatchLength, source.length()); \
43 const LChar* characters8 = source.characters8(); \
44 for (unsigned i = 0; i < bufferLength; ++i) \
45 characterBuffer[i] = characters8[i]; \
46 name = characterBuffer; \
49 using namespace HTMLNames;
51 inline HTMLMetaElement::HTMLMetaElement(Document& document)
52 : HTMLElement(metaTag, document)
54 ScriptWrappable::init(this);
57 DEFINE_NODE_FACTORY(HTMLMetaElement)
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.
75 unsigned keyBegin, keyEnd;
76 unsigned valueBegin, valueEnd;
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 string
82 while (isSeparator(buffer[i])) {
89 // skip to first separator
90 while (!isSeparator(buffer[i])) {
91 error |= isInvalidSeparator(buffer[i]);
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)
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)
114 // skip to first separator
115 while (!isSeparator(buffer[i])) {
116 error |= isInvalidSeparator(buffer[i]);
123 ASSERT_WITH_SECURITY_IMPLICATION(i <= length);
125 String keyString = buffer.substring(keyBegin, keyEnd - keyBegin);
126 String valueString = buffer.substring(valueBegin, valueEnd - valueBegin);
127 (this->*callback)(keyString, valueString, data);
130 String message = "Error parsing a meta element's content: ';' is not a valid key-value pair separator. Please use ',' instead.";
131 document().addConsoleMessage(RenderingMessageSource, WarningMessageLevel, message);
135 static inline float clampLengthValue(float value)
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)));
143 static inline float clampScaleValue(float value)
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)));
151 float HTMLMetaElement::parsePositiveNumber(const String& keyString, const String& valueString, bool* ok)
155 if (valueString.is8Bit())
156 value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength);
158 value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength);
160 reportViewportWarning(UnrecognizedViewportArgumentValueError, valueString, keyString);
165 if (parsedLength < valueString.length())
166 reportViewportWarning(TruncatedViewportArgumentValueError, valueString, keyString);
172 Length HTMLMetaElement::parseViewportValueAsLength(const String& keyString, const String& valueString)
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.
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);
185 CASE("device-height") {
186 return Length(DeviceHeight);
190 float value = parsePositiveNumber(keyString, valueString);
193 return Length(); // auto
195 return Length(clampLengthValue(value), Fixed);
198 float HTMLMetaElement::parseViewportValueAsZoom(const String& keyString, const String& valueString, bool& computedValueMatchesParsedValue)
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
206 computedValueMatchesParsedValue = false;
207 unsigned length = valueString.length();
208 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
209 SWITCH(characters, length) {
216 CASE("device-width") {
219 CASE("device-height") {
224 float value = parsePositiveNumber(keyString, valueString);
227 return ViewportDescription::ValueAuto;
230 reportViewportWarning(MaximumScaleTooLargeError, String(), String());
232 if (!value && document().settings() && document().settings()->viewportMetaZeroValuesQuirk())
233 return ViewportDescription::ValueAuto;
235 float clampedValue = clampScaleValue(value);
236 if (clampedValue == value)
237 computedValueMatchesParsedValue = true;
242 bool HTMLMetaElement::parseViewportValueAsUserZoom(const String& keyString, const String& valueString, bool& computedValueMatchesParsedValue)
244 // yes and no are used as keywords.
245 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
246 // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
248 computedValueMatchesParsedValue = false;
249 unsigned length = valueString.length();
250 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
251 SWITCH(characters, length) {
253 computedValueMatchesParsedValue = true;
257 computedValueMatchesParsedValue = true;
260 CASE("device-width") {
263 CASE("device-height") {
268 float value = parsePositiveNumber(keyString, valueString);
275 float HTMLMetaElement::parseViewportValueAsDPI(const String& keyString, const String& valueString)
277 unsigned length = valueString.length();
278 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10);
279 SWITCH(characters, length) {
281 return ViewportDescription::ValueDeviceDPI;
284 return ViewportDescription::ValueLowDPI;
287 return ViewportDescription::ValueMediumDPI;
290 return ViewportDescription::ValueHighDPI;
295 float value = parsePositiveNumber(keyString, valueString, &ok);
296 if (!ok || value < 70 || value > 400)
297 return ViewportDescription::ValueAuto;
302 void HTMLMetaElement::processViewportKeyValuePair(const String& keyString, const String& valueString, void* data)
304 ViewportDescription* description = static_cast<ViewportDescription*>(data);
306 unsigned length = keyString.length();
308 DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17);
309 SWITCH(characters, length) {
311 const Length& width = parseViewportValueAsLength(keyString, valueString);
314 description->minWidth = Length(ExtendToZoom);
315 description->maxWidth = width;
319 const Length& height = parseViewportValueAsLength(keyString, valueString);
322 description->minHeight = Length(ExtendToZoom);
323 description->maxHeight = height;
326 CASE("initial-scale") {
327 description->zoom = parseViewportValueAsZoom(keyString, valueString, description->zoomIsExplicit);
330 CASE("minimum-scale") {
331 description->minZoom = parseViewportValueAsZoom(keyString, valueString, description->minZoomIsExplicit);
334 CASE("maximum-scale") {
335 description->maxZoom = parseViewportValueAsZoom(keyString, valueString, description->maxZoomIsExplicit);
338 CASE("user-scalable") {
339 description->userZoom = parseViewportValueAsUserZoom(keyString, valueString, description->userZoomIsExplicit);
342 CASE("target-densitydpi") {
343 description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(keyString, valueString);
344 reportViewportWarning(TargetDensityDpiUnsupported, String(), String());
348 // Ignore vendor-specific argument.
352 reportViewportWarning(UnrecognizedViewportArgumentKeyError, keyString, String());
355 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
357 static const char* const errors[] = {
358 "The key \"%replacement1\" is not recognized and ignored.",
359 "The value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.",
360 "The value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
361 "The value for key \"maximum-scale\" is out of bounds and the value has been clamped.",
362 "The key \"target-densitydpi\" is not supported.",
365 return errors[errorCode];
368 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
371 case TruncatedViewportArgumentValueError:
372 case TargetDensityDpiUnsupported:
373 case UnrecognizedViewportArgumentKeyError:
374 case UnrecognizedViewportArgumentValueError:
375 case MaximumScaleTooLargeError:
376 return WarningMessageLevel;
379 ASSERT_NOT_REACHED();
380 return ErrorMessageLevel;
383 void HTMLMetaElement::reportViewportWarning(ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
385 if (!document().frame())
388 String message = viewportErrorMessageTemplate(errorCode);
389 if (!replacement1.isNull())
390 message.replace("%replacement1", replacement1);
391 if (!replacement2.isNull())
392 message.replace("%replacement2", replacement2);
394 // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
395 document().addConsoleMessage(RenderingMessageSource, viewportErrorMessageLevel(errorCode), message);
398 void HTMLMetaElement::processViewportContentAttribute(const String& content, ViewportDescription::Type origin)
400 ASSERT(!content.isNull());
402 if (!document().shouldOverrideLegacyDescription(origin))
405 ViewportDescription descriptionFromLegacyTag(origin);
406 if (document().shouldMergeWithLegacyDescription(origin))
407 descriptionFromLegacyTag = document().viewportDescription();
409 parseContentAttribute(content, &HTMLMetaElement::processViewportKeyValuePair, (void*)&descriptionFromLegacyTag);
411 if (descriptionFromLegacyTag.minZoom == ViewportDescription::ValueAuto)
412 descriptionFromLegacyTag.minZoom = 0.25;
414 if (descriptionFromLegacyTag.maxZoom == ViewportDescription::ValueAuto) {
415 descriptionFromLegacyTag.maxZoom = 5;
416 descriptionFromLegacyTag.minZoom = std::min(descriptionFromLegacyTag.minZoom, float(5));
419 document().setViewportDescription(descriptionFromLegacyTag);
423 void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
425 if (name == http_equivAttr || name == contentAttr) {
430 if (name != nameAttr)
431 HTMLElement::parseAttribute(name, value);
434 Node::InsertionNotificationRequest HTMLMetaElement::insertedInto(ContainerNode* insertionPoint)
436 HTMLElement::insertedInto(insertionPoint);
437 return InsertionShouldCallDidNotifySubtreeInsertions;
440 void HTMLMetaElement::didNotifySubtreeInsertionsToDocument()
445 static bool inDocumentHead(HTMLMetaElement* element)
447 if (!element->inDocument())
450 for (Element* current = element; current; current = current->parentElement()) {
451 if (isHTMLHeadElement(*current))
457 void HTMLMetaElement::process()
462 // All below situations require a content attribute (which can be the empty string).
463 const AtomicString& contentValue = fastGetAttribute(contentAttr);
464 if (contentValue.isNull())
467 const AtomicString& nameValue = fastGetAttribute(nameAttr);
468 if (!nameValue.isEmpty()) {
469 if (equalIgnoringCase(nameValue, "viewport"))
470 processViewportContentAttribute(contentValue, ViewportDescription::ViewportMeta);
471 else if (equalIgnoringCase(nameValue, "referrer"))
472 document().processReferrerPolicy(contentValue);
473 else if (equalIgnoringCase(nameValue, "handheldfriendly") && equalIgnoringCase(contentValue, "true"))
474 processViewportContentAttribute("width=device-width", ViewportDescription::HandheldFriendlyMeta);
475 else if (equalIgnoringCase(nameValue, "mobileoptimized"))
476 processViewportContentAttribute("width=device-width, initial-scale=1", ViewportDescription::MobileOptimizedMeta);
477 else if (RuntimeEnabledFeatures::themeColorEnabled() && equalIgnoringCase(nameValue, "theme-color") && document().frame())
478 document().frame()->loader().client()->dispatchDidChangeThemeColor();
481 // Get the document to process the tag, but only if we're actually part of DOM
482 // tree (changing a meta tag while it's not in the tree shouldn't have any effect
485 const AtomicString& httpEquivValue = fastGetAttribute(http_equivAttr);
486 if (!httpEquivValue.isEmpty())
487 document().processHttpEquiv(httpEquivValue, contentValue, inDocumentHead(this));
490 const AtomicString& HTMLMetaElement::content() const
492 return getAttribute(contentAttr);
495 const AtomicString& HTMLMetaElement::httpEquiv() const
497 return getAttribute(http_equivAttr);
500 const AtomicString& HTMLMetaElement::name() const
502 return getNameAttribute();