This source file includes following definitions.
- create
- isInvalidSeparator
- isSeparator
- parseContentAttribute
- clampLengthValue
- clampScaleValue
- parsePositiveNumber
- parseViewportValueAsLength
- parseViewportValueAsZoom
- parseViewportValueAsUserZoom
- parseViewportValueAsDPI
- processViewportKeyValuePair
- viewportErrorMessageTemplate
- viewportErrorMessageLevel
- reportViewportWarning
- processViewportContentAttribute
- parseAttribute
- insertedInto
- didNotifySubtreeInsertionsToDocument
- inDocumentHead
- process
- content
- httpEquiv
- name
#include "config.h"
#include "core/html/HTMLMetaElement.h"
#include "HTMLNames.h"
#include "core/dom/Document.h"
#include "core/frame/Settings.h"
namespace WebCore {
#define DEFINE_ARRAY_FOR_MATCHING(name, source, maxMatchLength) \
const UChar* name; \
const unsigned uMaxMatchLength = maxMatchLength; \
UChar characterBuffer[uMaxMatchLength]; \
if (!source.is8Bit()) { \
name = source.characters16(); \
} else { \
unsigned bufferLength = std::min(uMaxMatchLength, source.length()); \
const LChar* characters8 = source.characters8(); \
for (unsigned i = 0; i < bufferLength; ++i) \
characterBuffer[i] = characters8[i]; \
name = characterBuffer; \
}
using namespace HTMLNames;
inline HTMLMetaElement::HTMLMetaElement(Document& document)
: HTMLElement(metaTag, document)
{
ScriptWrappable::init(this);
}
PassRefPtr<HTMLMetaElement> HTMLMetaElement::create(Document& document)
{
return adoptRef(new HTMLMetaElement(document));
}
static bool isInvalidSeparator(UChar c)
{
return c == ';';
}
static bool isSeparator(UChar c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0';
}
void HTMLMetaElement::parseContentAttribute(const String& content, KeyValuePairCallback callback, void* data)
{
bool error = false;
int keyBegin, keyEnd;
int valueBegin, valueEnd;
int i = 0;
int length = content.length();
String buffer = content.lower();
while (i < length) {
while (isSeparator(buffer[i])) {
if (i >= length)
break;
i++;
}
keyBegin = i;
while (!isSeparator(buffer[i])) {
error |= isInvalidSeparator(buffer[i]);
i++;
}
keyEnd = i;
while (buffer[i] != '=') {
error |= isInvalidSeparator(buffer[i]);
if (buffer[i] == ',' || i >= length)
break;
i++;
}
while (isSeparator(buffer[i])) {
if (buffer[i] == ',' || i >= length)
break;
i++;
}
valueBegin = i;
while (!isSeparator(buffer[i])) {
error |= isInvalidSeparator(buffer[i]);
i++;
}
valueEnd = i;
ASSERT_WITH_SECURITY_IMPLICATION(i <= length);
String keyString = buffer.substring(keyBegin, keyEnd - keyBegin);
String valueString = buffer.substring(valueBegin, valueEnd - valueBegin);
(this->*callback)(keyString, valueString, data);
}
if (error) {
String message = "Error parsing a meta element's content: ';' is not a valid key-value pair separator. Please use ',' instead.";
document().addConsoleMessage(RenderingMessageSource, WarningMessageLevel, message);
}
}
static inline float clampLengthValue(float value)
{
if (value != ViewportDescription::ValueAuto)
return std::min(float(10000), std::max(value, float(1)));
return value;
}
static inline float clampScaleValue(float value)
{
if (value != ViewportDescription::ValueAuto)
return std::min(float(10), std::max(value, float(0.1)));
return value;
}
float HTMLMetaElement::parsePositiveNumber(const String& keyString, const String& valueString, bool* ok)
{
size_t parsedLength;
float value;
if (valueString.is8Bit())
value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength);
else
value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength);
if (!parsedLength) {
reportViewportWarning(UnrecognizedViewportArgumentValueError, valueString, keyString);
if (ok)
*ok = false;
return 0;
}
if (parsedLength < valueString.length())
reportViewportWarning(TruncatedViewportArgumentValueError, valueString, keyString);
if (ok)
*ok = true;
return value;
}
Length HTMLMetaElement::parseViewportValueAsLength(const String& keyString, const String& valueString)
{
unsigned length = valueString.length();
DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
SWITCH(characters, length) {
CASE("device-width") {
return Length(DeviceWidth);
}
CASE("device-height") {
return Length(DeviceHeight);
}
}
float value = parsePositiveNumber(keyString, valueString);
if (value < 0)
return Length();
return Length(clampLengthValue(value), Fixed);
}
float HTMLMetaElement::parseViewportValueAsZoom(const String& keyString, const String& valueString)
{
unsigned length = valueString.length();
DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
SWITCH(characters, length) {
CASE("yes") {
return 1;
}
CASE("no") {
return 0;
}
CASE("device-width") {
return 10;
}
CASE("device-height") {
return 10;
}
}
float value = parsePositiveNumber(keyString, valueString);
if (value < 0)
return ViewportDescription::ValueAuto;
if (value > 10.0)
reportViewportWarning(MaximumScaleTooLargeError, String(), String());
if (!value && document().settings() && document().settings()->viewportMetaZeroValuesQuirk())
return ViewportDescription::ValueAuto;
return clampScaleValue(value);
}
float HTMLMetaElement::parseViewportValueAsUserZoom(const String& keyString, const String& valueString)
{
unsigned length = valueString.length();
DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
SWITCH(characters, length) {
CASE("yes") {
return 1;
}
CASE("no") {
return 0;
}
CASE("device-width") {
return 1;
}
CASE("device-height") {
return 1;
}
}
float value = parsePositiveNumber(keyString, valueString);
if (fabs(value) < 1)
return 0;
return 1;
}
float HTMLMetaElement::parseViewportValueAsDPI(const String& keyString, const String& valueString)
{
unsigned length = valueString.length();
DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10);
SWITCH(characters, length) {
CASE("device-dpi") {
return ViewportDescription::ValueDeviceDPI;
}
CASE("low-dpi") {
return ViewportDescription::ValueLowDPI;
}
CASE("medium-dpi") {
return ViewportDescription::ValueMediumDPI;
}
CASE("high-dpi") {
return ViewportDescription::ValueHighDPI;
}
}
bool ok;
float value = parsePositiveNumber(keyString, valueString, &ok);
if (!ok || value < 70 || value > 400)
return ViewportDescription::ValueAuto;
return value;
}
void HTMLMetaElement::processViewportKeyValuePair(const String& keyString, const String& valueString, void* data)
{
ViewportDescription* description = static_cast<ViewportDescription*>(data);
unsigned length = keyString.length();
DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17);
SWITCH(characters, length) {
CASE("width") {
const Length& width = parseViewportValueAsLength(keyString, valueString);
if (width.isAuto())
return;
description->minWidth = Length(ExtendToZoom);
description->maxWidth = width;
return;
}
CASE("height") {
const Length& height = parseViewportValueAsLength(keyString, valueString);
if (height.isAuto())
return;
description->minHeight = Length(ExtendToZoom);
description->maxHeight = height;
return;
}
CASE("initial-scale") {
description->zoom = parseViewportValueAsZoom(keyString, valueString);
return;
}
CASE("minimum-scale") {
description->minZoom = parseViewportValueAsZoom(keyString, valueString);
return;
}
CASE("maximum-scale") {
description->maxZoom = parseViewportValueAsZoom(keyString, valueString);
return;
}
CASE("user-scalable") {
description->userZoom = parseViewportValueAsUserZoom(keyString, valueString);
return;
}
CASE("target-densitydpi") {
description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(keyString, valueString);
reportViewportWarning(TargetDensityDpiUnsupported, String(), String());
return;
}
CASE("minimal-ui") {
return;
}
}
reportViewportWarning(UnrecognizedViewportArgumentKeyError, keyString, String());
}
static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
{
static const char* const errors[] = {
"The key \"%replacement1\" is not recognized and ignored.",
"The value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.",
"The value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
"The value for key \"maximum-scale\" is out of bounds and the value has been clamped.",
"The key \"target-densitydpi\" is not supported.",
};
return errors[errorCode];
}
static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
{
switch (errorCode) {
case TruncatedViewportArgumentValueError:
case TargetDensityDpiUnsupported:
case UnrecognizedViewportArgumentKeyError:
case UnrecognizedViewportArgumentValueError:
case MaximumScaleTooLargeError:
return WarningMessageLevel;
}
ASSERT_NOT_REACHED();
return ErrorMessageLevel;
}
void HTMLMetaElement::reportViewportWarning(ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
{
if (!document().frame())
return;
String message = viewportErrorMessageTemplate(errorCode);
if (!replacement1.isNull())
message.replace("%replacement1", replacement1);
if (!replacement2.isNull())
message.replace("%replacement2", replacement2);
document().addConsoleMessage(RenderingMessageSource, viewportErrorMessageLevel(errorCode), message);
}
void HTMLMetaElement::processViewportContentAttribute(const String& content, ViewportDescription::Type origin)
{
ASSERT(!content.isNull());
if (!document().shouldOverrideLegacyDescription(origin))
return;
ViewportDescription descriptionFromLegacyTag(origin);
if (document().shouldMergeWithLegacyDescription(origin))
descriptionFromLegacyTag = document().viewportDescription();
parseContentAttribute(content, &HTMLMetaElement::processViewportKeyValuePair, (void*)&descriptionFromLegacyTag);
if (descriptionFromLegacyTag.minZoom == ViewportDescription::ValueAuto)
descriptionFromLegacyTag.minZoom = 0.25;
if (descriptionFromLegacyTag.maxZoom == ViewportDescription::ValueAuto) {
descriptionFromLegacyTag.maxZoom = 5;
descriptionFromLegacyTag.minZoom = std::min(descriptionFromLegacyTag.minZoom, float(5));
}
document().setViewportDescription(descriptionFromLegacyTag);
}
void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
{
if (name == http_equivAttr || name == contentAttr) {
process();
return;
}
if (name != nameAttr)
HTMLElement::parseAttribute(name, value);
}
Node::InsertionNotificationRequest HTMLMetaElement::insertedInto(ContainerNode* insertionPoint)
{
HTMLElement::insertedInto(insertionPoint);
return InsertionShouldCallDidNotifySubtreeInsertions;
}
void HTMLMetaElement::didNotifySubtreeInsertionsToDocument()
{
process();
}
static bool inDocumentHead(HTMLMetaElement* element)
{
if (!element->inDocument())
return false;
for (Element* current = element; current; current = current->parentElement()) {
if (isHTMLHeadElement(*current))
return true;
}
return false;
}
void HTMLMetaElement::process()
{
if (!inDocument())
return;
const AtomicString& contentValue = fastGetAttribute(contentAttr);
if (contentValue.isNull())
return;
const AtomicString& nameValue = fastGetAttribute(nameAttr);
if (!nameValue.isEmpty()) {
if (equalIgnoringCase(nameValue, "viewport"))
processViewportContentAttribute(contentValue, ViewportDescription::ViewportMeta);
else if (equalIgnoringCase(nameValue, "referrer"))
document().processReferrerPolicy(contentValue);
else if (equalIgnoringCase(nameValue, "handheldfriendly") && equalIgnoringCase(contentValue, "true"))
processViewportContentAttribute("width=device-width", ViewportDescription::HandheldFriendlyMeta);
else if (equalIgnoringCase(nameValue, "mobileoptimized"))
processViewportContentAttribute("width=device-width, initial-scale=1", ViewportDescription::MobileOptimizedMeta);
}
const AtomicString& httpEquivValue = fastGetAttribute(http_equivAttr);
if (!httpEquivValue.isEmpty())
document().processHttpEquiv(httpEquivValue, contentValue, inDocumentHead(this));
}
const AtomicString& HTMLMetaElement::content() const
{
return getAttribute(contentAttr);
}
const AtomicString& HTMLMetaElement::httpEquiv() const
{
return getAttribute(http_equivAttr);
}
const AtomicString& HTMLMetaElement::name() const
{
return getNameAttribute();
}
}