Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(711)

Unified Diff: Source/core/html/HTMLMetaElement-in.cpp

Issue 19555002: Translate viewport related meta tags into @viewport descriptors as suggested by the CSS Device Adap… (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/core/html/HTMLMetaElement-in.cpp
diff --git a/Source/core/html/HTMLMetaElement-in.cpp b/Source/core/html/HTMLMetaElement-in.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8761610c0741654ae8012a40411d326caff59081
--- /dev/null
+++ b/Source/core/html/HTMLMetaElement-in.cpp
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2001 Dirk Mueller (mueller@kde.org)
+ * Copyright (C) 2003, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2013 Intel Corporation. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "core/html/HTMLMetaElement.h"
+
+#include "HTMLNames.h"
+#include "RuntimeEnabledFeatures.h"
+#include "core/css/CSSStyleSheet.h"
+#include "core/css/CSSValuePool.h"
+#include "core/css/MediaList.h"
+#include "core/css/StylePropertySet.h"
+#include "core/css/StyleSheetContents.h"
+#include "core/dom/Document.h"
+#include "core/dom/DocumentStyleSheetCollection.h"
+#include "wtf/text/StringBuilder.h"
+#include "wtf/text/TextPosition.h"
+
+namespace WebCore {
+
+using namespace HTMLNames;
+using namespace std;
+
+inline HTMLMetaElement::HTMLMetaElement(const QualifiedName& tagName, Document* document)
+ : HTMLElement(tagName, document)
+{
+ ASSERT(hasTagName(metaTag));
+ ScriptWrappable::init(this);
+}
+
+HTMLMetaElement::~HTMLMetaElement()
+{
+ removeStyleSheet();
+}
+
+PassRefPtr<HTMLMetaElement> HTMLMetaElement::create(Document* document)
+{
+ return adoptRef(new HTMLMetaElement(metaTag, document));
+}
+
+PassRefPtr<HTMLMetaElement> HTMLMetaElement::create(const QualifiedName& tagName, Document* document)
+{
+ return adoptRef(new HTMLMetaElement(tagName, document));
+}
+
+void HTMLMetaElement::removedFrom(ContainerNode* insertionPoint)
+{
+ HTMLElement::removedFrom(insertionPoint);
+ removeStyleSheet();
+}
+
+Node::InsertionNotificationRequest HTMLMetaElement::insertedInto(ContainerNode* insertionPoint)
+{
+ HTMLElement::insertedInto(insertionPoint);
+ if (insertionPoint->inDocument())
+ process();
+ return InsertionDone;
+}
+
+
+static inline bool isValueSeparator(UChar c, bool* ok)
+{
+ if (ok && c == ';')
+ *ok = false;
+ // Though isspace() considers \t and \v to be whitespace, Win IE doesn't.
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0';
+}
+
+static inline bool isPropertySeparator(UChar c, bool* ok)
+{
+ if (ok && c == ';')
+ *ok = false;
+ return c == ',';
+}
+
+float HTMLMetaElement::parsePositiveNumber(const String& property, const String& value, float minValue, float maxValue)
+{
+ size_t parsedLength;
+ float rawValue;
+ float fValue;
+ if (value.is8Bit())
+ rawValue = charactersToFloat(value.characters8(), value.length(), parsedLength);
+ else
+ rawValue = charactersToFloat(value.characters16(), value.length(), parsedLength);
+
+ if (!parsedLength || rawValue < 0) {
+ reportError(InvalidValueError, property, value);
+ return -1;
+ }
+
+ if (parsedLength < value.length())
+ reportError(TruncatedValueError, property, value);
+
+ fValue = min(maxValue, max(rawValue, minValue));
+ if (fValue != rawValue)
+ reportError(OutOfBoundsValueError, property, value);
+
+ return fValue;
+}
+
+void HTMLMetaElement::reportError(ErrorType error, const String& property, const String& value)
+{
+ StringBuilder builder;
+
+ switch (error) {
+ case NoError:
+ return;
+
+ case InvalidPropertySeparatorError:
+ builder.appendLiteral("Note that ';' is not a property separator. The list should be comma-separated.");
+ break;
+
+ case InvalidPropertyError:
+ builder.appendLiteral("Property \"");
+ builder.append(property);
+ builder.appendLiteral("\" not recognized and ignored.");
+ break;
+
+ case InvalidValueError:
+ case OutOfBoundsValueError:
+ case TruncatedValueError:
+ if (!value.isEmpty()) {
+ builder.appendLiteral("The value \"");
+ builder.append(value);
+ builder.append('\"');
+ } else {
+ builder.appendLiteral("The empty value");
+ }
+
+ if (!property.isNull()) {
+ builder.appendLiteral(" for property \"");
+ builder.append(property);
+ builder.append('\"');
+ }
+
+ if (error == InvalidValueError)
+ builder.appendLiteral(" is invalid and the property has been ignored.");
+ else if (error == OutOfBoundsValueError)
+ builder.appendLiteral(" is out of bounds and the value has been clamped.");
+ else
+ builder.appendLiteral(" was truncated to its numeric prefix.");
+ break;
+
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ document()->addConsoleMessage(MetaContentMessageSource, WarningMessageLevel, builder.toString());
+}
+
+void HTMLMetaElement::parseContentAttribute(MutableStylePropertySet* properties, const String& content, PropertyCollector callback)
+{
+ bool ok = true;
+
+ // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior.
+ int keyBegin, keyEnd;
+ int valueBegin, valueEnd;
+
+ int i = 0;
+ int length = content.length();
+ String buffer = content.lower();
+ while (i < length) {
+ // Skip to first non-separator, but don't skip past the end of the string.
+ while (isValueSeparator(buffer[i], &ok)) {
+ if (i >= length)
+ break;
+ i++;
+ }
+ keyBegin = i;
+
+ // skip to first separator
+ while (!isValueSeparator(buffer[i], &ok))
+ i++;
+ keyEnd = i;
+
+ // Skip to first '=', but don't skip past a ',' or the end of the string.
+ while (buffer[i] != '=') {
+ if (isPropertySeparator(buffer[i], &ok) || i >= length)
+ break;
+ i++;
+ }
+
+ // Skip to first non-separator, but don't skip past a ',' or the end of the string.
+ while (isValueSeparator(buffer[i], &ok)) {
+ if (isPropertySeparator(buffer[i], &ok) || i >= length)
+ break;
+ i++;
+ }
+ valueBegin = i;
+
+ // Skip to first separator.
+ while (!isValueSeparator(buffer[i], &ok))
+ i++;
+ valueEnd = i;
+
+ ASSERT_WITH_SECURITY_IMPLICATION(i <= length);
+
+ String propertyString = buffer.substring(keyBegin, keyEnd - keyBegin);
+ String valueString = buffer.substring(valueBegin, valueEnd - valueBegin);
+
+ callback(this, properties, propertyString, valueString);
+ }
+ if (!ok)
+ reportError(InvalidPropertySeparatorError, String(), String());
+}
+
+PassRefPtr<CSSValue> HTMLMetaElement::parseViewportValueAsLength(const String& propertyString, const String& valueString)
+{
+ const UChar* characters;
+ unsigned valueLength = valueString.length();
+
+ const unsigned longestValueLength = 13;
+ UChar characterBuffer[longestValueLength];
+ if (valueString.is8Bit()) {
+ unsigned length = std::min(longestValueLength, valueLength);
+ const LChar* characters8 = valueString.characters8();
+ for (unsigned i = 0; i < length; ++i)
+ characterBuffer[i] = characters8[i];
+ characters = characterBuffer;
+ } else {
+ characters = valueString.characters16();
+ }
+
+ SWITCH(characters, valueLength) {
+ CASE("device-width") {
+ return cssValuePool().createValue(100, CSSPrimitiveValue::CSS_PERCENTAGE);
+ }
+ CASE("device-height") {
+ return cssValuePool().createValue(100, CSSPrimitiveValue::CSS_PERCENTAGE);
+ }
+ }
+
+ // Other keywords and unknown values translate to 1px.
+ float value = parsePositiveNumber(propertyString, valueString, float(1), float(10000));
+ if (value >= 0)
+ return cssValuePool().createValue(value, CSSPrimitiveValue::CSS_PX);
+
+ return 0;
+}
+
+PassRefPtr<CSSValue> HTMLMetaElement::parseViewportValueAsZoom(const String& propertyString, const String& valueString)
+{
+ const UChar* characters;
+ unsigned valueLength = valueString.length();
+
+ const unsigned longestValueLength = 13;
+ UChar characterBuffer[longestValueLength];
+ if (valueString.is8Bit()) {
+ unsigned length = std::min(longestValueLength, valueLength);
+ const LChar* characters8 = valueString.characters8();
+ for (unsigned i = 0; i < length; ++i)
+ characterBuffer[i] = characters8[i];
+ characters = characterBuffer;
+ } else {
+ characters = valueString.characters16();
+ }
+
+ float value = -1;
+ SWITCH(characters, valueLength) {
+ CASE("yes") {
+ value = 1;
+ }
+ CASE("no") {
+ value = 0.1;
+ }
+ CASE("device-width") {
+ value = 10;
+ }
+ CASE("device-height") {
+ value = 10;
+ }
+ }
+
+ if (value < 0)
+ value = parsePositiveNumber(propertyString, valueString, float(0.1), float(10));
+ if (value >= 0)
+ return cssValuePool().createValue(value, CSSPrimitiveValue::CSS_NUMBER);
+
+ return 0;
+}
+
+CSSValueID HTMLMetaElement::parseViewportValueAsUserZoom(const String& propertyString, const String& valueString)
+{
+ const UChar* characters;
+ unsigned valueLength = valueString.length();
+
+ const unsigned longestValueLength = 13;
+ UChar characterBuffer[longestValueLength];
+ if (valueString.is8Bit()) {
+ unsigned length = std::min(longestValueLength, valueLength);
+ const LChar* characters8 = valueString.characters8();
+ for (unsigned i = 0; i < length; ++i)
+ characterBuffer[i] = characters8[i];
+ characters = characterBuffer;
+ } else {
+ characters = valueString.characters16();
+ }
+
+ SWITCH(characters, valueLength) {
+ CASE("yes") {
+ return CSSValueZoom;
+ }
+ CASE("no") {
+ return CSSValueFixed;
+ }
+ CASE("device-width") {
+ return CSSValueZoom;
+ }
+ CASE("device-height") {
+ return CSSValueZoom;
+ }
+ }
+
+ float value = parsePositiveNumber(propertyString, valueString, float(0), float(1));
+ // Numbers in the range <-1, 1>, and unknown values (represented as 0), are mapped to "fixed".
+ // Numbers >= 1, numbers <= -1 are mapped to "zoom"
+ return (value > -1 && value < 1) ? CSSValueFixed : CSSValueZoom;
+}
+
+PassRefPtr<CSSValue> HTMLMetaElement::parseViewportValueAsDPI(const String& propertyString, const String& valueString)
+{
+ const UChar* characters;
+ unsigned valueLength = valueString.length();
+
+ const unsigned longestValueLength = 10;
+ UChar characterBuffer[longestValueLength];
+ if (valueString.is8Bit()) {
+ unsigned length = std::min(longestValueLength, valueLength);
+ const LChar* characters8 = valueString.characters8();
+ for (unsigned i = 0; i < length; ++i)
+ characterBuffer[i] = characters8[i];
+ characters = characterBuffer;
+ } else {
+ characters = valueString.characters16();
+ }
+
+ CSSValueID id = CSSValueAuto; // Fallback for invalid.
+ SWITCH(characters, valueLength) {
+ CASE("device-dpi") {
+ id = CSSValueDevice;
+ }
+ CASE("low-dpi") {
+ id = CSSValueSmall;
+ }
+ CASE("medium-dpi") {
+ id = CSSValueMedium;
+ }
+ CASE("high-dpi") {
+ id = CSSValueLarge;
+ }
+ }
+
+ if (id == CSSValueAuto) {
+ float value = parsePositiveNumber(propertyString, valueString, float(70), float(400));
+
+ if (value >= 70 && value <= 400)
+ return cssValuePool().createValue(value, CSSPrimitiveValue::CSS_NUMBER);
+ }
+
+ return cssValuePool().createIdentifierValue(id);
+}
+
+void HTMLMetaElement::viewportPropertyCollector(HTMLMetaElement* self, MutableStylePropertySet* properties, const String& propertyString, const String& valueString)
+{
+ const UChar* characters;
+ unsigned propertyLength = propertyString.length();
+
+ const unsigned longestPropertyLength = 17;
+ UChar characterBuffer[longestPropertyLength];
+ if (propertyString.is8Bit()) {
+ unsigned length = std::min(longestPropertyLength, propertyLength);
+ const LChar* characters8 = propertyString.characters8();
+ for (unsigned i = 0; i < length; ++i)
+ characterBuffer[i] = characters8[i];
+ characters = characterBuffer;
+ } else {
+ characters = propertyString.characters16();
+ }
+
+ SWITCH(characters, propertyLength) {
+ CASE("width") {
+ RefPtr<CSSValue> value = self->parseViewportValueAsLength(propertyString, valueString);
+ if (!value)
+ return;
+ properties->setProperty(CSSPropertyMinWidth, CSSValueInternalExtendToZoom);
+ properties->setProperty(CSSPropertyMaxWidth, value);
+ return;
+ }
+ CASE("height") {
+ RefPtr<CSSValue> value = self->parseViewportValueAsLength(propertyString, valueString);
+ if (!value)
+ return;
+ properties->setProperty(CSSPropertyMinHeight, CSSValueInternalExtendToZoom);
+ properties->setProperty(CSSPropertyMaxHeight, value);
+ return;
+ }
+ CASE("initial-scale") {
+ if (RefPtr<CSSValue> value = self->parseViewportValueAsZoom(propertyString, valueString))
+ properties->setProperty(CSSPropertyZoom, value);
+ return;
+ }
+ CASE("minimum-scale") {
+ if (RefPtr<CSSValue> value = self->parseViewportValueAsZoom(propertyString, valueString))
+ properties->setProperty(CSSPropertyMinZoom, value);
+ return;
+ }
+ CASE("maximum-scale") {
+ if (RefPtr<CSSValue> value = self->parseViewportValueAsZoom(propertyString, valueString))
+ properties->setProperty(CSSPropertyMaxZoom, value);
+ return;
+ }
+ CASE("user-scalable") {
+ CSSValueID value = self->parseViewportValueAsUserZoom(propertyString, valueString);
+ properties->setProperty(CSSPropertyUserZoom, value);
+ return;
+ }
+ CASE("target-densitydpi") {
+ RefPtr<CSSValue> value = self->parseViewportValueAsDPI(propertyString, valueString);
+ properties->setProperty(CSSPropertyInternalTargetDensity, value);
+ return;
+ }
+ }
+ self->reportError(InvalidPropertyError, propertyString, String());
+}
+
+PassRefPtr<StyleRuleBase> HTMLMetaElement::parseViewportContent(const String& contentValue)
+{
+ RefPtr<StyleRuleViewport> rule = StyleRuleViewport::create();
+ RefPtr<MutableStylePropertySet> properties = MutableStylePropertySet::create(CSSStrictMode);
+ parseContentAttribute(properties.get(), contentValue, &HTMLMetaElement::viewportPropertyCollector);
+
+ // For a viewport META element that translates into an @viewport rule with a
+ // non-"auto" "zoom" declaration and no "width" declaration:
+ // If it adds a "height" descriptor, add: width: auto; Otherwise, add: width: extend-to-zoom;
+ RefPtr<CSSValue> widthValue = properties->getPropertyCSSValue(CSSPropertyMinWidth);
+ RefPtr<CSSValue> zoomValue = properties->getPropertyCSSValue(CSSPropertyZoom);
+
+ if (!widthValue && zoomValue) {
+ RefPtr<CSSValue> heightValue = properties->getPropertyCSSValue(CSSPropertyMinHeight);
+ CSSValueID commonHeightValue = heightValue ? CSSValueAuto : CSSValueInternalExtendToZoom;
+ properties->setProperty(CSSPropertyMinWidth, commonHeightValue);
+ properties->setProperty(CSSPropertyMaxWidth, commonHeightValue);
+ }
+
+ rule->setProperties(properties);
+
+ return rule.release();
+}
+
+PassRefPtr<StyleRuleBase> HTMLMetaElement::parseHandheldFriendlyContent(const String& contentValue)
+{
+ RefPtr<StyleRuleViewport> rule = StyleRuleViewport::create();
+ RefPtr<MutableStylePropertySet> properties = MutableStylePropertySet::create(CSSStrictMode);
+ properties->setProperty(CSSPropertyInternalPriority, cssValuePool().createValue(2, CSSPrimitiveValue::CSS_NUMBER));
+
+ if (equalIgnoringCase(contentValue, "true")) {
+ properties->setProperty(CSSPropertyMinWidth, cssValuePool().createValue(100, CSSPrimitiveValue::CSS_PERCENTAGE));
+ properties->setProperty(CSSPropertyMaxWidth, cssValuePool().createValue(100, CSSPrimitiveValue::CSS_PERCENTAGE));
+ }
+
+ rule->setProperties(properties);
+ return rule.release();
+}
+
+PassRefPtr<StyleRuleBase> HTMLMetaElement::parseMobileOptimizedContent(const String& contentValue)
+{
+ RefPtr<StyleRuleViewport> rule = StyleRuleViewport::create();
+ RefPtr<MutableStylePropertySet> properties = MutableStylePropertySet::create(CSSStrictMode);
+ properties->setProperty(CSSPropertyInternalPriority, cssValuePool().createValue(3, CSSPrimitiveValue::CSS_NUMBER));
+
+ float value = parsePositiveNumber(String(), contentValue, float(0), float(10000));
+ RefPtr<CSSValue> widthValue;
+ if (value > 0)
+ widthValue = cssValuePool().createValue(value, CSSPrimitiveValue::CSS_PX);
+ else
+ widthValue = cssValuePool().createValue(100, CSSPrimitiveValue::CSS_PERCENTAGE);
+
+ properties->setProperty(CSSPropertyMinWidth, CSSValueAuto);
+ properties->setProperty(CSSPropertyMaxWidth, widthValue);
+
+ rule->setProperties(properties);
+ return rule.release();
+}
+
+void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
+{
+ if (name == http_equivAttr || name == contentAttr) {
+ process();
+ } else if (name == nameAttr) {
+ // Do nothing.
+ } else {
+ HTMLElement::parseAttribute(name, value);
+ }
+}
+
+void HTMLMetaElement::process()
+{
+ // We always need to clear rules as the meta tag could have been modified.
+ removeStyleSheet();
+
+ if (!inDocument())
+ return;
+
+ // All below situations requires a content attribute (which can be the empty string).
+ const AtomicString& contentValue = fastGetAttribute(contentAttr);
+ if (contentValue.isNull())
+ return;
+
+ const AtomicString& nameValue = fastGetAttribute(nameAttr);
+
+ if (nameValue.isNull()) {
+ // Get the document to process the tag, but only if we're actually part of DOM tree (changing a meta tag while
+ // it's not in the tree shouldn't have any effect on the document)
+ const AtomicString& httpEquivValue = fastGetAttribute(http_equivAttr);
+ if (!httpEquivValue.isNull())
+ document()->processHttpEquiv(httpEquivValue, contentValue);
+ return;
+ }
+
+ if (equalIgnoringCase(nameValue, "viewport")) {
+ addStyleSheetForRule(parseViewportContent(contentValue));
+ } else if (equalIgnoringCase(name(), "handheldfriendly")) {
+ addStyleSheetForRule(parseHandheldFriendlyContent(contentValue));
+ } else if (equalIgnoringCase(name(), "mobileoptimized")) {
+ addStyleSheetForRule(parseMobileOptimizedContent(contentValue));
+ } else if (equalIgnoringCase(name(), "referrer")) {
+ document()->processReferrerPolicy(contentValue);
+ }
+}
+
+void HTMLMetaElement::addStyleSheetForRule(PassRefPtr<StyleRuleBase> rule)
+{
+ ASSERT(inDocument());
+ ASSERT(!m_sheet);
+
+ document()->styleSheetCollection()->addStyleSheetCandidateNode(this, false);
+
+ document()->styleSheetCollection()->addPendingSheet();
+
+ // As the stylesheet has no source URL, the position makes no difference.
+ TextPosition startPosition = TextPosition::minimumPosition();
+
+ m_sheet = CSSStyleSheet::createInline(this, KURL(), startPosition, document()->inputEncoding());
+
+ m_sheet->setMediaQueries(MediaQuerySet::create("screen"));
+ m_sheet->setTitle(title());
+
+ m_sheet->contents()->parserAppendRule(rule);
+
+ document()->styleSheetCollection()->removePendingSheet(m_sheet->ownerNode());
+}
+
+void HTMLMetaElement::removeStyleSheet()
+{
+ if (m_sheet) {
+ document()->styleSheetCollection()->removeStyleSheetCandidateNode(this);
+ m_sheet.release()->clearOwnerNode();
+ }
+
+ // No need to resolve style during teardown.
+ if (document()->renderer())
+ document()->styleResolverChanged(DeferRecalcStyle);
+}
+
+String HTMLMetaElement::content() const
+{
+ return getAttribute(contentAttr);
+}
+
+String HTMLMetaElement::httpEquiv() const
+{
+ return getAttribute(http_equivAttr);
+}
+
+String HTMLMetaElement::name() const
+{
+ return getNameAttribute();
+}
+
+}

Powered by Google App Engine
This is Rietveld 408576698