summaryrefslogtreecommitdiff
path: root/Source/WebCore/css/parser
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/css/parser')
-rw-r--r--Source/WebCore/css/parser/CSSAtRuleID.cpp67
-rw-r--r--Source/WebCore/css/parser/CSSAtRuleID.h55
-rw-r--r--Source/WebCore/css/parser/CSSDeferredParser.cpp57
-rw-r--r--Source/WebCore/css/parser/CSSDeferredParser.h68
-rw-r--r--Source/WebCore/css/parser/CSSParser.cpp303
-rw-r--r--Source/WebCore/css/parser/CSSParser.h89
-rw-r--r--Source/WebCore/css/parser/CSSParserFastPaths.cpp1258
-rw-r--r--Source/WebCore/css/parser/CSSParserFastPaths.h55
-rw-r--r--Source/WebCore/css/parser/CSSParserIdioms.cpp58
-rw-r--r--Source/WebCore/css/parser/CSSParserIdioms.h67
-rw-r--r--Source/WebCore/css/parser/CSSParserImpl.cpp910
-rw-r--r--Source/WebCore/css/parser/CSSParserImpl.h176
-rw-r--r--Source/WebCore/css/parser/CSSParserMode.h165
-rw-r--r--Source/WebCore/css/parser/CSSParserObserver.h47
-rw-r--r--Source/WebCore/css/parser/CSSParserObserverWrapper.cpp72
-rw-r--r--Source/WebCore/css/parser/CSSParserObserverWrapper.h77
-rw-r--r--Source/WebCore/css/parser/CSSParserSelector.cpp245
-rw-r--r--Source/WebCore/css/parser/CSSParserSelector.h135
-rw-r--r--Source/WebCore/css/parser/CSSParserToken.cpp479
-rw-r--r--Source/WebCore/css/parser/CSSParserToken.h178
-rw-r--r--Source/WebCore/css/parser/CSSParserTokenRange.cpp119
-rw-r--r--Source/WebCore/css/parser/CSSParserTokenRange.h104
-rw-r--r--Source/WebCore/css/parser/CSSPropertyParser.cpp5445
-rw-r--r--Source/WebCore/css/parser/CSSPropertyParser.h117
-rw-r--r--Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp1348
-rw-r--r--Source/WebCore/css/parser/CSSPropertyParserHelpers.h122
-rw-r--r--Source/WebCore/css/parser/CSSSelectorParser.cpp891
-rw-r--r--Source/WebCore/css/parser/CSSSelectorParser.h108
-rw-r--r--Source/WebCore/css/parser/CSSSupportsParser.cpp127
-rw-r--r--Source/WebCore/css/parser/CSSSupportsParser.h61
-rw-r--r--Source/WebCore/css/parser/CSSTokenizer.cpp879
-rw-r--r--Source/WebCore/css/parser/CSSTokenizer.h128
-rw-r--r--Source/WebCore/css/parser/CSSTokenizerInputStream.cpp73
-rw-r--r--Source/WebCore/css/parser/CSSTokenizerInputStream.h107
-rw-r--r--Source/WebCore/css/parser/CSSVariableParser.cpp166
-rw-r--r--Source/WebCore/css/parser/CSSVariableParser.h50
-rw-r--r--Source/WebCore/css/parser/MediaQueryBlockWatcher.cpp51
-rw-r--r--Source/WebCore/css/parser/MediaQueryBlockWatcher.h46
-rw-r--r--Source/WebCore/css/parser/MediaQueryParser.cpp310
-rw-r--r--Source/WebCore/css/parser/MediaQueryParser.h136
-rw-r--r--Source/WebCore/css/parser/SizesAttributeParser.cpp166
-rw-r--r--Source/WebCore/css/parser/SizesAttributeParser.h64
-rw-r--r--Source/WebCore/css/parser/SizesCalcParser.cpp259
-rw-r--r--Source/WebCore/css/parser/SizesCalcParser.h81
44 files changed, 15519 insertions, 0 deletions
diff --git a/Source/WebCore/css/parser/CSSAtRuleID.cpp b/Source/WebCore/css/parser/CSSAtRuleID.cpp
new file mode 100644
index 000000000..c0c3144c9
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSAtRuleID.cpp
@@ -0,0 +1,67 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSAtRuleID.h"
+
+namespace WebCore {
+
+CSSAtRuleID cssAtRuleID(StringView name)
+{
+ if (equalIgnoringASCIICase(name, "charset"))
+ return CSSAtRuleCharset;
+ if (equalIgnoringASCIICase(name, "font-face"))
+ return CSSAtRuleFontFace;
+ if (equalIgnoringASCIICase(name, "import"))
+ return CSSAtRuleImport;
+ if (equalIgnoringASCIICase(name, "keyframes"))
+ return CSSAtRuleKeyframes;
+ if (equalIgnoringASCIICase(name, "media"))
+ return CSSAtRuleMedia;
+ if (equalIgnoringASCIICase(name, "namespace"))
+ return CSSAtRuleNamespace;
+ if (equalIgnoringASCIICase(name, "page"))
+ return CSSAtRulePage;
+ if (equalIgnoringASCIICase(name, "supports"))
+ return CSSAtRuleSupports;
+ if (equalIgnoringASCIICase(name, "viewport"))
+ return CSSAtRuleViewport;
+ if (equalIgnoringASCIICase(name, "-webkit-keyframes"))
+ return CSSAtRuleWebkitKeyframes;
+ if (equalIgnoringASCIICase(name, "apply"))
+ return CSSAtRuleApply;
+#if ENABLE(CSS_REGIONS)
+ if (equalIgnoringASCIICase(name, "-webkit-region"))
+ return CSSAtRuleWebkitRegion;
+#endif
+ return CSSAtRuleInvalid;
+}
+
+} // namespace WebCore
+
diff --git a/Source/WebCore/css/parser/CSSAtRuleID.h b/Source/WebCore/css/parser/CSSAtRuleID.h
new file mode 100644
index 000000000..e360a7fd2
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSAtRuleID.h
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+#include <wtf/text/StringView.h>
+
+namespace WebCore {
+
+enum CSSAtRuleID {
+ CSSAtRuleInvalid = 0,
+
+ CSSAtRuleCharset = 1,
+ CSSAtRuleFontFace = 2,
+ CSSAtRuleImport = 3,
+ CSSAtRuleKeyframes = 4,
+ CSSAtRuleMedia = 5,
+ CSSAtRuleNamespace = 6,
+ CSSAtRulePage = 7,
+ CSSAtRuleSupports = 8,
+ CSSAtRuleViewport = 9,
+
+ CSSAtRuleWebkitKeyframes = 10,
+ CSSAtRuleApply = 11,
+ CSSAtRuleWebkitRegion = 12
+};
+
+CSSAtRuleID cssAtRuleID(StringView name);
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSDeferredParser.cpp b/Source/WebCore/css/parser/CSSDeferredParser.cpp
new file mode 100644
index 000000000..06469ea9f
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSDeferredParser.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CSSDeferredParser.h"
+
+#include "CSSParserImpl.h"
+#include "StyleSheetContents.h"
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+CSSDeferredParser::CSSDeferredParser(const CSSParserContext& context, const String& sheetText, StyleSheetContents& styleSheet)
+ : m_context(context)
+ , m_sheetText(sheetText)
+ , m_styleSheet(styleSheet.createWeakPtr())
+{
+}
+
+Ref<ImmutableStyleProperties> CSSDeferredParser::parseDeclaration(const CSSParserTokenRange& range)
+{
+ return CSSParserImpl::parseDeferredDeclaration(range, m_context, m_styleSheet.get());
+}
+
+void CSSDeferredParser::parseRuleList(const CSSParserTokenRange& range, Vector<RefPtr<StyleRuleBase>>& childRules)
+{
+ CSSParserImpl::parseDeferredRuleList(range, *this, childRules);
+}
+
+void CSSDeferredParser::parseKeyframeList(const CSSParserTokenRange& range, StyleRuleKeyframes&keyframesRule)
+{
+ CSSParserImpl::parseDeferredKeyframeList(range, *this, keyframesRule);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSDeferredParser.h b/Source/WebCore/css/parser/CSSDeferredParser.h
new file mode 100644
index 000000000..9b09d767a
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSDeferredParser.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CSSParserMode.h"
+#include "CSSTokenizer.h"
+#include <wtf/RefCounted.h>
+#include <wtf/WeakPtr.h>
+
+namespace WebCore {
+
+class ImmutableStyleProperties;
+class StyleRuleKeyframes;
+class StyleRuleBase;
+
+class CSSDeferredParser : public RefCounted<CSSDeferredParser> {
+public:
+ static Ref<CSSDeferredParser> create(const CSSParserContext& parserContext, const String& sheetText, StyleSheetContents& styleSheet)
+ {
+ return adoptRef(*new CSSDeferredParser(parserContext, sheetText, styleSheet));
+ }
+
+ CSSParserMode mode() const { return m_context.mode; }
+
+ const CSSParserContext& context() const { return m_context; }
+ StyleSheetContents* styleSheet() const { return m_styleSheet.get(); }
+
+ Ref<ImmutableStyleProperties> parseDeclaration(const CSSParserTokenRange&);
+ void parseRuleList(const CSSParserTokenRange&, Vector<RefPtr<StyleRuleBase>>&);
+ void parseKeyframeList(const CSSParserTokenRange&, StyleRuleKeyframes&);
+
+ void adoptTokenizerEscapedStrings(Vector<String>&& strings) { m_escapedStrings = WTFMove(strings); }
+
+private:
+ CSSDeferredParser(const CSSParserContext&, const String&, StyleSheetContents&);
+
+ Vector<String> m_escapedStrings;
+ CSSParserContext m_context;
+
+ String m_sheetText;
+
+ WeakPtr<StyleSheetContents> m_styleSheet;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParser.cpp b/Source/WebCore/css/parser/CSSParser.cpp
new file mode 100644
index 000000000..4af6ecfc8
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParser.cpp
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2003 Lars Knoll (knoll@kde.org)
+ * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
+ * Copyright (C) 2004-2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com>
+ * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
+ * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
+ * Copyright (C) 2012, 2013 Adobe Systems Incorporated. All rights reserved.
+ * Copyright (C) 2012 Intel Corporation. All rights reserved.
+ * Copyright (C) 2014 Google Inc. 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 "CSSParser.h"
+
+#include "CSSKeyframeRule.h"
+#include "CSSParserFastPaths.h"
+#include "CSSParserImpl.h"
+#include "CSSPendingSubstitutionValue.h"
+#include "CSSPropertyParser.h"
+#include "CSSSelectorParser.h"
+#include "CSSSupportsParser.h"
+#include "CSSTokenizer.h"
+#include "CSSVariableData.h"
+#include "CSSVariableReferenceValue.h"
+#include "Document.h"
+#include "Element.h"
+#include "Page.h"
+#include "RenderTheme.h"
+#include "RuntimeEnabledFeatures.h"
+#include "Settings.h"
+#include "StyleColor.h"
+#include "StyleRule.h"
+#include "StyleSheetContents.h"
+#include <wtf/NeverDestroyed.h>
+#include <wtf/text/StringBuilder.h>
+
+using namespace WTF;
+
+namespace WebCore {
+
+const CSSParserContext& strictCSSParserContext()
+{
+ static NeverDestroyed<CSSParserContext> strictContext(HTMLStandardMode);
+ return strictContext;
+}
+
+CSSParserContext::CSSParserContext(CSSParserMode mode, const URL& baseURL)
+ : baseURL(baseURL)
+ , mode(mode)
+ , cssGridLayoutEnabled(RuntimeEnabledFeatures::sharedFeatures().isCSSGridLayoutEnabled())
+{
+#if PLATFORM(IOS)
+ // FIXME: Force the site specific quirk below to work on iOS. Investigating other site specific quirks
+ // to see if we can enable the preference all together is to be handled by:
+ // <rdar://problem/8493309> Investigate Enabling Site Specific Quirks in MobileSafari and UIWebView
+ needsSiteSpecificQuirks = true;
+#endif
+}
+
+CSSParserContext::CSSParserContext(Document& document, const URL& baseURL, const String& charset)
+ : baseURL(baseURL.isNull() ? document.baseURL() : baseURL)
+ , charset(charset)
+ , mode(document.inQuirksMode() ? HTMLQuirksMode : HTMLStandardMode)
+ , isHTMLDocument(document.isHTMLDocument())
+ , cssGridLayoutEnabled(document.isCSSGridLayoutEnabled())
+{
+ needsSiteSpecificQuirks = document.settings().needsSiteSpecificQuirks();
+ enforcesCSSMIMETypeInNoQuirksMode = document.settings().enforceCSSMIMETypeInNoQuirksMode();
+ useLegacyBackgroundSizeShorthandBehavior = document.settings().useLegacyBackgroundSizeShorthandBehavior();
+#if ENABLE(TEXT_AUTOSIZING)
+ textAutosizingEnabled = document.settings().textAutosizingEnabled();
+#endif
+ springTimingFunctionEnabled = document.settings().springTimingFunctionEnabled();
+ deferredCSSParserEnabled = document.settings().deferredCSSParserEnabled();
+
+#if PLATFORM(IOS)
+ // FIXME: Force the site specific quirk below to work on iOS. Investigating other site specific quirks
+ // to see if we can enable the preference all together is to be handled by:
+ // <rdar://problem/8493309> Investigate Enabling Site Specific Quirks in MobileSafari and UIWebView
+ needsSiteSpecificQuirks = true;
+#endif
+}
+
+bool operator==(const CSSParserContext& a, const CSSParserContext& b)
+{
+ return a.baseURL == b.baseURL
+ && a.charset == b.charset
+ && a.mode == b.mode
+ && a.isHTMLDocument == b.isHTMLDocument
+ && a.cssGridLayoutEnabled == b.cssGridLayoutEnabled
+ && a.needsSiteSpecificQuirks == b.needsSiteSpecificQuirks
+ && a.enforcesCSSMIMETypeInNoQuirksMode == b.enforcesCSSMIMETypeInNoQuirksMode
+ && a.useLegacyBackgroundSizeShorthandBehavior == b.useLegacyBackgroundSizeShorthandBehavior
+ && a.springTimingFunctionEnabled == b.springTimingFunctionEnabled
+ && a.deferredCSSParserEnabled == b.deferredCSSParserEnabled;
+}
+
+CSSParser::CSSParser(const CSSParserContext& context)
+ : m_context(context)
+{
+}
+
+CSSParser::~CSSParser()
+{
+}
+
+void CSSParser::parseSheet(StyleSheetContents* sheet, const String& string, RuleParsing ruleParsing)
+{
+ return CSSParserImpl::parseStyleSheet(string, m_context, sheet, ruleParsing);
+}
+
+void CSSParser::parseSheetForInspector(const CSSParserContext& context, StyleSheetContents* sheet, const String& string, CSSParserObserver& observer)
+{
+ return CSSParserImpl::parseStyleSheetForInspector(string, context, sheet, observer);
+}
+
+RefPtr<StyleRuleBase> CSSParser::parseRule(const CSSParserContext& context, StyleSheetContents* sheet, const String& string)
+{
+ return CSSParserImpl::parseRule(string, context, sheet, CSSParserImpl::AllowImportRules);
+}
+
+RefPtr<StyleRuleKeyframe> CSSParser::parseKeyframeRule(const String& string)
+{
+ RefPtr<StyleRuleBase> keyframe = CSSParserImpl::parseRule(string, m_context, nullptr, CSSParserImpl::KeyframeRules);
+ return downcast<StyleRuleKeyframe>(keyframe.get());
+}
+
+bool CSSParser::parseSupportsCondition(const String& condition)
+{
+ CSSParserImpl parser(m_context, condition);
+ return CSSSupportsParser::supportsCondition(parser.tokenizer()->tokenRange(), parser) == CSSSupportsParser::Supported;
+}
+
+Color CSSParser::parseColor(const String& string, bool strict)
+{
+ if (string.isEmpty())
+ return Color();
+
+ // Try named colors first.
+ Color namedColor { string };
+ if (namedColor.isValid())
+ return namedColor;
+
+ // Try the fast path to parse hex and rgb.
+ RefPtr<CSSValue> value = CSSParserFastPaths::parseColor(string, strict ? HTMLStandardMode : HTMLQuirksMode);
+
+ // If that fails, try the full parser.
+ if (!value)
+ value = parseSingleValue(CSSPropertyColor, string, strictCSSParserContext());
+ if (!value || !value->isPrimitiveValue())
+ return Color();
+ const auto& primitiveValue = downcast<CSSPrimitiveValue>(*value);
+ if (!primitiveValue.isRGBColor())
+ return Color();
+ return primitiveValue.color();
+}
+
+Color CSSParser::parseSystemColor(const String& string, Document* document)
+{
+ if (!document || !document->page())
+ return Color();
+
+ CSSValueID id = cssValueKeywordID(string);
+ if (!StyleColor::isSystemColor(id))
+ return Color();
+
+ return document->page()->theme().systemColor(id);
+}
+
+RefPtr<CSSValue> CSSParser::parseSingleValue(CSSPropertyID propertyID, const String& string, const CSSParserContext& context)
+{
+ if (string.isEmpty())
+ return nullptr;
+ if (RefPtr<CSSValue> value = CSSParserFastPaths::maybeParseValue(propertyID, string, context.mode))
+ return value;
+ CSSTokenizer tokenizer(string);
+ return CSSPropertyParser::parseSingleValue(propertyID, tokenizer.tokenRange(), context, nullptr);
+}
+
+CSSParser::ParseResult CSSParser::parseValue(MutableStyleProperties& declaration, CSSPropertyID propertyID, const String& string, bool important, const CSSParserContext& context)
+{
+ ASSERT(!string.isEmpty());
+ RefPtr<CSSValue> value = CSSParserFastPaths::maybeParseValue(propertyID, string, context.mode);
+ if (value)
+ return declaration.addParsedProperty(CSSProperty(propertyID, WTFMove(value), important)) ? CSSParser::ParseResult::Changed : CSSParser::ParseResult::Unchanged;
+
+ CSSParser parser(context);
+ return parser.parseValue(declaration, propertyID, string, important);
+}
+
+CSSParser::ParseResult CSSParser::parseCustomPropertyValue(MutableStyleProperties& declaration, const AtomicString& propertyName, const String& string, bool important, const CSSParserContext& context)
+{
+ return CSSParserImpl::parseCustomPropertyValue(&declaration, propertyName, string, important, context);
+}
+
+CSSParser::ParseResult CSSParser::parseValue(MutableStyleProperties& declaration, CSSPropertyID propertyID, const String& string, bool important)
+{
+ return CSSParserImpl::parseValue(&declaration, propertyID, string, important, m_context);
+}
+
+void CSSParser::parseSelector(const String& string, CSSSelectorList& selectorList)
+{
+ CSSTokenizer tokenizer(string);
+ selectorList = CSSSelectorParser::parseSelector(tokenizer.tokenRange(), m_context, nullptr);
+}
+
+Ref<ImmutableStyleProperties> CSSParser::parseInlineStyleDeclaration(const String& string, Element* element)
+{
+ CSSParserContext context(element->document());
+ context.mode = strictToCSSParserMode(element->isHTMLElement() && !element->document().inQuirksMode());
+ return CSSParserImpl::parseInlineStyleDeclaration(string, element);
+}
+
+bool CSSParser::parseDeclaration(MutableStyleProperties& declaration, const String& string)
+{
+ return CSSParserImpl::parseDeclarationList(&declaration, string, m_context);
+}
+
+void CSSParser::parseDeclarationForInspector(const CSSParserContext& context, const String& string, CSSParserObserver& observer)
+{
+ CSSParserImpl::parseDeclarationListForInspector(string, context, observer);
+}
+
+RefPtr<CSSValue> CSSParser::parseValueWithVariableReferences(CSSPropertyID propID, const CSSValue& value, const CustomPropertyValueMap& customProperties, TextDirection direction, WritingMode writingMode)
+{
+ if (value.isPendingSubstitutionValue()) {
+ // FIXME: Should have a resolvedShorthands cache to stop this from being done
+ // over and over for each longhand value.
+ const CSSPendingSubstitutionValue& pendingSubstitution = downcast<CSSPendingSubstitutionValue>(value);
+ CSSPropertyID shorthandID = pendingSubstitution.shorthandPropertyId();
+ if (CSSProperty::isDirectionAwareProperty(shorthandID))
+ shorthandID = CSSProperty::resolveDirectionAwareProperty(shorthandID, direction, writingMode);
+ CSSVariableReferenceValue* shorthandValue = pendingSubstitution.shorthandValue();
+ const CSSVariableData* variableData = shorthandValue->variableDataValue();
+ ASSERT(variableData);
+
+ Vector<CSSParserToken> resolvedTokens;
+ if (!variableData->resolveTokenRange(customProperties, variableData->tokens(), resolvedTokens))
+ return nullptr;
+
+ ParsedPropertyVector parsedProperties;
+ if (!CSSPropertyParser::parseValue(shorthandID, false, resolvedTokens, m_context, nullptr, parsedProperties, StyleRule::Style))
+ return nullptr;
+
+ for (auto& property : parsedProperties) {
+ if (property.id() == propID)
+ return property.value();
+ }
+
+ return nullptr;
+ }
+
+ if (value.isVariableReferenceValue()) {
+ const CSSVariableReferenceValue& valueWithReferences = downcast<CSSVariableReferenceValue>(value);
+ const CSSVariableData* variableData = valueWithReferences.variableDataValue();
+ ASSERT(variableData);
+
+ Vector<CSSParserToken> resolvedTokens;
+ if (!variableData->resolveTokenRange(customProperties, variableData->tokens(), resolvedTokens))
+ return nullptr;
+
+ return CSSPropertyParser::parseSingleValue(propID, resolvedTokens, m_context, nullptr);
+ }
+
+ return nullptr;
+}
+
+std::unique_ptr<Vector<double>> CSSParser::parseKeyframeKeyList(const String& selector)
+{
+ return CSSParserImpl::parseKeyframeKeyList(selector);
+}
+
+RefPtr<CSSValue> CSSParser::parseFontFaceDescriptor(CSSPropertyID propertyID, const String& propertyValue, const CSSParserContext& context)
+{
+ StringBuilder builder;
+ builder.append("@font-face { ");
+ builder.append(getPropertyNameString(propertyID));
+ builder.append(" : ");
+ builder.append(propertyValue);
+ builder.append("; }");
+ RefPtr<StyleRuleBase> rule = parseRule(context, nullptr, builder.toString());
+ if (!rule || !rule->isFontFaceRule())
+ return nullptr;
+ return downcast<StyleRuleFontFace>(*rule.get()).properties().getPropertyCSSValue(propertyID);
+}
+
+}
diff --git a/Source/WebCore/css/parser/CSSParser.h b/Source/WebCore/css/parser/CSSParser.h
new file mode 100644
index 000000000..0991dff40
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParser.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003 Lars Knoll (knoll@kde.org)
+ * Copyright (C) 2004-2010, 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
+ * Copyright (C) 2009 - 2010 Torch Mobile (Beijing) Co. Ltd. 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.
+ */
+
+#pragma once
+
+#include "CSSParserMode.h"
+#include "CSSValue.h"
+#include "WritingMode.h"
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CSSParserObserver;
+class CSSSelectorList;
+class Color;
+class Element;
+class ImmutableStyleProperties;
+class MutableStyleProperties;
+class StyleRuleBase;
+class StyleRuleKeyframe;
+class StyleSheetContents;
+
+class CSSParser {
+public:
+ enum class ParseResult {
+ Changed,
+ Unchanged,
+ Error
+ };
+
+ WEBCORE_EXPORT explicit CSSParser(const CSSParserContext&);
+ WEBCORE_EXPORT ~CSSParser();
+
+ enum class RuleParsing { Normal, Deferred };
+ void parseSheet(StyleSheetContents*, const String&, RuleParsing = RuleParsing::Normal);
+
+ static RefPtr<StyleRuleBase> parseRule(const CSSParserContext&, StyleSheetContents*, const String&);
+
+ RefPtr<StyleRuleKeyframe> parseKeyframeRule(const String&);
+ static std::unique_ptr<Vector<double>> parseKeyframeKeyList(const String&);
+
+ bool parseSupportsCondition(const String&);
+
+ static void parseSheetForInspector(const CSSParserContext&, StyleSheetContents*, const String&, CSSParserObserver&);
+ static void parseDeclarationForInspector(const CSSParserContext&, const String&, CSSParserObserver&);
+
+ static ParseResult parseValue(MutableStyleProperties&, CSSPropertyID, const String&, bool important, const CSSParserContext&);
+ static ParseResult parseCustomPropertyValue(MutableStyleProperties&, const AtomicString& propertyName, const String&, bool important, const CSSParserContext&);
+
+ static RefPtr<CSSValue> parseFontFaceDescriptor(CSSPropertyID, const String&, const CSSParserContext&);
+
+ static RefPtr<CSSValue> parseSingleValue(CSSPropertyID, const String&, const CSSParserContext& = strictCSSParserContext());
+
+ WEBCORE_EXPORT bool parseDeclaration(MutableStyleProperties&, const String&);
+ static Ref<ImmutableStyleProperties> parseInlineStyleDeclaration(const String&, Element*);
+
+ void parseSelector(const String&, CSSSelectorList&);
+
+ RefPtr<CSSValue> parseValueWithVariableReferences(CSSPropertyID, const CSSValue&, const CustomPropertyValueMap& customProperties, TextDirection, WritingMode);
+
+ static Color parseColor(const String&, bool strict = false);
+ static Color parseSystemColor(const String&, Document*);
+
+private:
+ ParseResult parseValue(MutableStyleProperties&, CSSPropertyID, const String&, bool important);
+
+ CSSParserContext m_context;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserFastPaths.cpp b/Source/WebCore/css/parser/CSSParserFastPaths.cpp
new file mode 100644
index 000000000..f563a9a6d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserFastPaths.cpp
@@ -0,0 +1,1258 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSParserFastPaths.h"
+
+#include "CSSFunctionValue.h"
+#include "CSSInheritedValue.h"
+#include "CSSInitialValue.h"
+#include "CSSParserIdioms.h"
+#include "CSSPrimitiveValue.h"
+#include "CSSPropertyParser.h"
+#include "CSSValueList.h"
+#include "CSSValuePool.h"
+#include "HTMLParserIdioms.h"
+#include "RuntimeEnabledFeatures.h"
+#include "StyleColor.h"
+#include "StylePropertyShorthand.h"
+#include "StyleSheetContents.h"
+
+namespace WebCore {
+
+static inline bool isSimpleLengthPropertyID(CSSPropertyID propertyId, bool& acceptsNegativeNumbers)
+{
+ switch (propertyId) {
+ case CSSPropertyFontSize:
+ case CSSPropertyGridColumnGap:
+ case CSSPropertyGridRowGap:
+ case CSSPropertyHeight:
+ case CSSPropertyWidth:
+ case CSSPropertyMinHeight:
+ case CSSPropertyMinWidth:
+ case CSSPropertyPaddingBottom:
+ case CSSPropertyPaddingLeft:
+ case CSSPropertyPaddingRight:
+ case CSSPropertyPaddingTop:
+ case CSSPropertyWebkitLogicalWidth:
+ case CSSPropertyWebkitLogicalHeight:
+ case CSSPropertyWebkitMinLogicalWidth:
+ case CSSPropertyWebkitMinLogicalHeight:
+ case CSSPropertyWebkitPaddingAfter:
+ case CSSPropertyWebkitPaddingBefore:
+ case CSSPropertyWebkitPaddingEnd:
+ case CSSPropertyWebkitPaddingStart:
+ case CSSPropertyShapeMargin:
+ acceptsNegativeNumbers = false;
+ return true;
+ case CSSPropertyBottom:
+ case CSSPropertyCx:
+ case CSSPropertyCy:
+ case CSSPropertyLeft:
+ case CSSPropertyMarginBottom:
+ case CSSPropertyMarginLeft:
+ case CSSPropertyMarginRight:
+ case CSSPropertyMarginTop:
+ case CSSPropertyRight:
+ case CSSPropertyTop:
+ case CSSPropertyWebkitMarginAfter:
+ case CSSPropertyWebkitMarginBefore:
+ case CSSPropertyWebkitMarginEnd:
+ case CSSPropertyWebkitMarginStart:
+ case CSSPropertyX:
+ case CSSPropertyY:
+ case CSSPropertyR:
+ case CSSPropertyRx:
+ case CSSPropertyRy:
+ acceptsNegativeNumbers = true;
+ return true;
+ default:
+ return false;
+ }
+}
+
+template <typename CharacterType>
+static inline bool parseSimpleLength(const CharacterType* characters, unsigned length, CSSPrimitiveValue::UnitType& unit, double& number)
+{
+ if (length > 2 && (characters[length - 2] | 0x20) == 'p' && (characters[length - 1] | 0x20) == 'x') {
+ length -= 2;
+ unit = CSSPrimitiveValue::UnitType::CSS_PX;
+ } else if (length > 1 && characters[length - 1] == '%') {
+ length -= 1;
+ unit = CSSPrimitiveValue::UnitType::CSS_PERCENTAGE;
+ }
+
+ // We rely on charactersToDouble for validation as well. The function
+ // will set "ok" to "false" if the entire passed-in character range does
+ // not represent a double.
+ bool ok;
+ number = charactersToDouble(characters, length, &ok);
+ if (!ok)
+ return false;
+ return true;
+}
+
+static RefPtr<CSSValue> parseSimpleLengthValue(CSSPropertyID propertyId, const String& string, CSSParserMode cssParserMode)
+{
+ ASSERT(!string.isEmpty());
+ bool acceptsNegativeNumbers = false;
+
+ // In @viewport, width and height are shorthands, not simple length values.
+ if (isCSSViewportParsingEnabledForMode(cssParserMode) || !isSimpleLengthPropertyID(propertyId, acceptsNegativeNumbers))
+ return nullptr;
+
+ unsigned length = string.length();
+ double number;
+ CSSPrimitiveValue::UnitType unit = CSSPrimitiveValue::UnitType::CSS_NUMBER;
+
+ if (string.is8Bit()) {
+ if (!parseSimpleLength(string.characters8(), length, unit, number))
+ return nullptr;
+ } else {
+ if (!parseSimpleLength(string.characters16(), length, unit, number))
+ return nullptr;
+ }
+
+ if (unit == CSSPrimitiveValue::UnitType::CSS_NUMBER) {
+ if (number && cssParserMode != SVGAttributeMode)
+ return nullptr;
+ unit = CSSPrimitiveValue::UnitType::CSS_PX;
+ }
+
+ if (number < 0 && !acceptsNegativeNumbers)
+ return nullptr;
+ if (std::isinf(number))
+ return nullptr;
+
+ return CSSPrimitiveValue::create(number, unit);
+}
+
+static inline bool isColorPropertyID(CSSPropertyID propertyId)
+{
+ switch (propertyId) {
+ case CSSPropertyColor:
+ case CSSPropertyBackgroundColor:
+ case CSSPropertyBorderBottomColor:
+ case CSSPropertyBorderLeftColor:
+ case CSSPropertyBorderRightColor:
+ case CSSPropertyBorderTopColor:
+ case CSSPropertyFill:
+ case CSSPropertyFloodColor:
+ case CSSPropertyLightingColor:
+ case CSSPropertyOutlineColor:
+ case CSSPropertyStopColor:
+ case CSSPropertyStroke:
+ case CSSPropertyWebkitBorderAfterColor:
+ case CSSPropertyWebkitBorderBeforeColor:
+ case CSSPropertyWebkitBorderEndColor:
+ case CSSPropertyWebkitBorderStartColor:
+ case CSSPropertyColumnRuleColor:
+ case CSSPropertyWebkitTextEmphasisColor:
+ case CSSPropertyWebkitTextFillColor:
+ case CSSPropertyWebkitTextStrokeColor:
+ case CSSPropertyWebkitTextDecorationColor:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Returns the number of characters which form a valid double
+// and are terminated by the given terminator character
+template <typename CharacterType>
+static int checkForValidDouble(const CharacterType* string, const CharacterType* end, const char terminator)
+{
+ int length = end - string;
+ if (length < 1)
+ return 0;
+
+ bool decimalMarkSeen = false;
+ int processedLength = 0;
+
+ for (int i = 0; i < length; ++i) {
+ if (string[i] == terminator) {
+ processedLength = i;
+ break;
+ }
+ if (!isASCIIDigit(string[i])) {
+ if (!decimalMarkSeen && string[i] == '.')
+ decimalMarkSeen = true;
+ else
+ return 0;
+ }
+ }
+
+ if (decimalMarkSeen && processedLength == 1)
+ return 0;
+
+ return processedLength;
+}
+
+// Returns the number of characters consumed for parsing a valid double
+// terminated by the given terminator character
+template <typename CharacterType>
+static int parseDouble(const CharacterType* string, const CharacterType* end, const char terminator, double& value)
+{
+ int length = checkForValidDouble(string, end, terminator);
+ if (!length)
+ return 0;
+
+ int position = 0;
+ double localValue = 0;
+
+ // The consumed characters here are guaranteed to be
+ // ASCII digits with or without a decimal mark
+ for (; position < length; ++position) {
+ if (string[position] == '.')
+ break;
+ localValue = localValue * 10 + string[position] - '0';
+ }
+
+ if (++position == length) {
+ value = localValue;
+ return length;
+ }
+
+ double fraction = 0;
+ double scale = 1;
+
+ const double maxScale = 1000000;
+ while (position < length && scale < maxScale) {
+ fraction = fraction * 10 + string[position++] - '0';
+ scale *= 10;
+ }
+
+ value = localValue + fraction / scale;
+ return length;
+}
+
+template <typename CharacterType>
+static bool parseColorIntOrPercentage(const CharacterType*& string, const CharacterType* end, const char terminator, CSSPrimitiveValue::UnitType& expect, int& value)
+{
+ const CharacterType* current = string;
+ double localValue = 0;
+ bool negative = false;
+ while (current != end && isHTMLSpace<CharacterType>(*current))
+ current++;
+ if (current != end && *current == '-') {
+ negative = true;
+ current++;
+ }
+ if (current == end || !isASCIIDigit(*current))
+ return false;
+ while (current != end && isASCIIDigit(*current)) {
+ double newValue = localValue * 10 + *current++ - '0';
+ if (newValue >= 255) {
+ // Clamp values at 255.
+ localValue = 255;
+ while (current != end && isASCIIDigit(*current))
+ ++current;
+ break;
+ }
+ localValue = newValue;
+ }
+
+ if (current == end)
+ return false;
+
+ if (expect == CSSPrimitiveValue::UnitType::CSS_NUMBER && (*current == '.' || *current == '%'))
+ return false;
+
+ if (*current == '.') {
+ // We already parsed the integral part, try to parse
+ // the fraction part of the percentage value.
+ double percentage = 0;
+ int numCharactersParsed = parseDouble(current, end, '%', percentage);
+ if (!numCharactersParsed)
+ return false;
+ current += numCharactersParsed;
+ if (*current != '%')
+ return false;
+ localValue += percentage;
+ }
+
+ if (expect == CSSPrimitiveValue::UnitType::CSS_PERCENTAGE && *current != '%')
+ return false;
+
+ if (*current == '%') {
+ expect = CSSPrimitiveValue::UnitType::CSS_PERCENTAGE;
+ localValue = localValue / 100.0 * 256.0;
+ // Clamp values at 255 for percentages over 100%
+ if (localValue > 255)
+ localValue = 255;
+ current++;
+ } else {
+ expect = CSSPrimitiveValue::UnitType::CSS_NUMBER;
+ }
+
+ while (current != end && isHTMLSpace<CharacterType>(*current))
+ current++;
+ if (current == end || *current++ != terminator)
+ return false;
+ // Clamp negative values at zero.
+ value = negative ? 0 : static_cast<int>(localValue);
+ string = current;
+ return true;
+}
+
+template <typename CharacterType>
+static inline bool isTenthAlpha(const CharacterType* string, const int length)
+{
+ // "0.X"
+ if (length == 3 && string[0] == '0' && string[1] == '.' && isASCIIDigit(string[2]))
+ return true;
+
+ // ".X"
+ if (length == 2 && string[0] == '.' && isASCIIDigit(string[1]))
+ return true;
+
+ return false;
+}
+
+template <typename CharacterType>
+static inline bool parseAlphaValue(const CharacterType*& string, const CharacterType* end, const char terminator, int& value)
+{
+ while (string != end && isHTMLSpace<CharacterType>(*string))
+ string++;
+
+ bool negative = false;
+
+ if (string != end && *string == '-') {
+ negative = true;
+ string++;
+ }
+
+ value = 0;
+
+ int length = end - string;
+ if (length < 2)
+ return false;
+
+ if (string[length - 1] != terminator || !isASCIIDigit(string[length - 2]))
+ return false;
+
+ if (string[0] != '0' && string[0] != '1' && string[0] != '.') {
+ if (checkForValidDouble(string, end, terminator)) {
+ value = negative ? 0 : 255;
+ string = end;
+ return true;
+ }
+ return false;
+ }
+
+ if (length == 2 && string[0] != '.') {
+ value = !negative && string[0] == '1' ? 255 : 0;
+ string = end;
+ return true;
+ }
+
+ if (isTenthAlpha(string, length - 1)) {
+ static const int tenthAlphaValues[] = { 0, 25, 51, 76, 102, 127, 153, 179, 204, 230 };
+ value = negative ? 0 : tenthAlphaValues[string[length - 2] - '0'];
+ string = end;
+ return true;
+ }
+
+ double alpha = 0;
+ if (!parseDouble(string, end, terminator, alpha))
+ return false;
+ value = negative ? 0 : static_cast<int>(alpha * nextafter(256.0, 0.0));
+ string = end;
+ return true;
+}
+
+template <typename CharacterType>
+static inline bool mightBeRGBA(const CharacterType* characters, unsigned length)
+{
+ if (length < 5)
+ return false;
+ return characters[4] == '('
+ && isASCIIAlphaCaselessEqual(characters[0], 'r')
+ && isASCIIAlphaCaselessEqual(characters[1], 'g')
+ && isASCIIAlphaCaselessEqual(characters[2], 'b')
+ && isASCIIAlphaCaselessEqual(characters[3], 'a');
+}
+
+template <typename CharacterType>
+static inline bool mightBeRGB(const CharacterType* characters, unsigned length)
+{
+ if (length < 4)
+ return false;
+ return characters[3] == '('
+ && isASCIIAlphaCaselessEqual(characters[0], 'r')
+ && isASCIIAlphaCaselessEqual(characters[1], 'g')
+ && isASCIIAlphaCaselessEqual(characters[2], 'b');
+}
+
+template <typename CharacterType>
+static Color fastParseColorInternal(const CharacterType* characters, unsigned length, bool quirksMode)
+{
+ CSSPrimitiveValue::UnitType expect = CSSPrimitiveValue::UnitType::CSS_UNKNOWN;
+
+ if (length >= 4 && characters[0] == '#') {
+ RGBA32 rgb;
+ if (Color::parseHexColor(characters + 1, length - 1, rgb))
+ return Color(rgb);
+ }
+
+ if (quirksMode && (length == 3 || length == 6)) {
+ RGBA32 rgb;
+ if (Color::parseHexColor(characters, length, rgb))
+ return Color(rgb);
+ }
+
+ // Try rgba() syntax.
+ if (mightBeRGBA(characters, length)) {
+ const CharacterType* current = characters + 5;
+ const CharacterType* end = characters + length;
+ int red;
+ int green;
+ int blue;
+ int alpha;
+
+ if (!parseColorIntOrPercentage(current, end, ',', expect, red))
+ return Color();
+ if (!parseColorIntOrPercentage(current, end, ',', expect, green))
+ return Color();
+ if (!parseColorIntOrPercentage(current, end, ',', expect, blue))
+ return Color();
+ if (!parseAlphaValue(current, end, ')', alpha))
+ return Color();
+ if (current != end)
+ return Color();
+ return Color(makeRGBA(red, green, blue, alpha));
+ }
+
+ // Try rgb() syntax.
+ if (mightBeRGB(characters, length)) {
+ const CharacterType* current = characters + 4;
+ const CharacterType* end = characters + length;
+ int red;
+ int green;
+ int blue;
+ if (!parseColorIntOrPercentage(current, end, ',', expect, red))
+ return Color();
+ if (!parseColorIntOrPercentage(current, end, ',', expect, green))
+ return Color();
+ if (!parseColorIntOrPercentage(current, end, ')', expect, blue))
+ return Color();
+ if (current != end)
+ return Color();
+ return Color(makeRGB(red, green, blue));
+ }
+
+ return Color();
+}
+
+RefPtr<CSSValue> CSSParserFastPaths::parseColor(const String& string, CSSParserMode parserMode)
+{
+ ASSERT(!string.isEmpty());
+ CSSValueID valueID = cssValueKeywordID(string);
+ if (StyleColor::isColorKeyword(valueID)) {
+ if (!isValueAllowedInMode(valueID, parserMode))
+ return nullptr;
+ return CSSValuePool::singleton().createIdentifierValue(valueID);
+ }
+
+ bool quirksMode = isQuirksModeBehavior(parserMode);
+
+ // Fast path for hex colors and rgb()/rgba() colors
+ Color color;
+ if (string.is8Bit())
+ color = fastParseColorInternal(string.characters8(), string.length(), quirksMode);
+ else
+ color = fastParseColorInternal(string.characters16(), string.length(), quirksMode);
+ if (!color.isValid())
+ return nullptr;
+ return CSSValuePool::singleton().createColorValue(color);
+}
+
+bool CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyID propertyId, CSSValueID valueID, CSSParserMode parserMode)
+{
+ if (valueID == CSSValueInvalid || !isValueAllowedInMode(valueID, parserMode))
+ return false;
+
+ switch (propertyId) {
+ case CSSPropertyAlignmentBaseline:
+ // auto | baseline | before-edge | text-before-edge | middle |
+ // central | after-edge | text-after-edge | ideographic | alphabetic |
+ // hanging | mathematical
+ return valueID == CSSValueAuto || valueID == CSSValueAlphabetic || valueID == CSSValueBaseline
+ || valueID == CSSValueMiddle || (valueID >= CSSValueBeforeEdge && valueID <= CSSValueMathematical);
+ case CSSPropertyAll:
+ return false; // Only accepts css-wide keywords
+ case CSSPropertyBackgroundRepeatX: // repeat | no-repeat
+ case CSSPropertyBackgroundRepeatY: // repeat | no-repeat
+ return valueID == CSSValueRepeat || valueID == CSSValueNoRepeat;
+ case CSSPropertyBorderCollapse: // collapse | separate
+ return valueID == CSSValueCollapse || valueID == CSSValueSeparate;
+ case CSSPropertyBorderTopStyle: // <border-style>
+ case CSSPropertyBorderRightStyle: // Defined as: none | hidden | dotted | dashed |
+ case CSSPropertyBorderBottomStyle: // solid | double | groove | ridge | inset | outset
+ case CSSPropertyBorderLeftStyle:
+ case CSSPropertyWebkitBorderAfterStyle:
+ case CSSPropertyWebkitBorderBeforeStyle:
+ case CSSPropertyWebkitBorderEndStyle:
+ case CSSPropertyWebkitBorderStartStyle:
+ case CSSPropertyColumnRuleStyle:
+ return valueID >= CSSValueNone && valueID <= CSSValueDouble;
+ case CSSPropertyBoxSizing:
+ return valueID == CSSValueBorderBox || valueID == CSSValueContentBox;
+ case CSSPropertyBufferedRendering:
+ return valueID == CSSValueAuto || valueID == CSSValueDynamic || valueID == CSSValueStatic;
+ case CSSPropertyCaptionSide: // top | bottom | left | right
+ return valueID == CSSValueLeft || valueID == CSSValueRight || valueID == CSSValueTop || valueID == CSSValueBottom;
+ case CSSPropertyClear: // none | left | right | both
+ return valueID == CSSValueNone || valueID == CSSValueLeft || valueID == CSSValueRight || valueID == CSSValueBoth;
+ case CSSPropertyClipRule:
+ case CSSPropertyFillRule:
+ return valueID == CSSValueNonzero || valueID == CSSValueEvenodd;
+ case CSSPropertyColorInterpolation:
+ case CSSPropertyColorInterpolationFilters:
+ return valueID == CSSValueAuto || valueID == CSSValueSRGB || valueID == CSSValueLinearRGB;
+ case CSSPropertyColorRendering:
+ return valueID == CSSValueAuto || valueID == CSSValueOptimizeSpeed || valueID == CSSValueOptimizeQuality;
+ case CSSPropertyDirection: // ltr | rtl
+ return valueID == CSSValueLtr || valueID == CSSValueRtl;
+ case CSSPropertyDisplay:
+ // inline | block | list-item | inline-block | table |
+ // inline-table | table-row-group | table-header-group | table-footer-group | table-row |
+ // table-column-group | table-column | table-cell | table-caption | -webkit-box | -webkit-inline-box | none
+ // flex | inline-flex | -webkit-flex | -webkit-inline-flex | grid | inline-grid
+ return (valueID >= CSSValueInline && valueID <= CSSValueContents) || valueID == CSSValueNone
+ || (RuntimeEnabledFeatures::sharedFeatures().isCSSGridLayoutEnabled() && (valueID == CSSValueGrid || valueID == CSSValueInlineGrid))
+ ;
+ case CSSPropertyDominantBaseline:
+ // auto | use-script | no-change | reset-size | ideographic |
+ // alphabetic | hanging | mathematical | central | middle |
+ // text-after-edge | text-before-edge
+ return valueID == CSSValueAuto || valueID == CSSValueAlphabetic || valueID == CSSValueMiddle
+ || (valueID >= CSSValueUseScript && valueID <= CSSValueResetSize)
+ || (valueID >= CSSValueCentral && valueID <= CSSValueMathematical);
+ case CSSPropertyEmptyCells: // show | hide
+ return valueID == CSSValueShow || valueID == CSSValueHide;
+ case CSSPropertyFloat: // left | right | none
+ return valueID == CSSValueLeft || valueID == CSSValueRight || valueID == CSSValueNone;
+ case CSSPropertyFontStyle: // normal | italic | oblique
+ return valueID == CSSValueNormal || valueID == CSSValueItalic || valueID == CSSValueOblique;
+ case CSSPropertyFontStretch: // normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded
+ return valueID == CSSValueNormal || (valueID >= CSSValueUltraCondensed && valueID <= CSSValueUltraExpanded);
+ case CSSPropertyImageRendering: // auto | optimizeContrast | pixelated | optimizeSpeed | crispEdges | optimizeQuality | webkit-crispEdges
+ return valueID == CSSValueAuto || valueID == CSSValueOptimizeSpeed || valueID == CSSValueOptimizeQuality || valueID == CSSValueWebkitCrispEdges || valueID == CSSValueWebkitOptimizeContrast || valueID == CSSValueCrispEdges || valueID == CSSValuePixelated;
+#if ENABLE(CSS_COMPOSITING)
+ case CSSPropertyIsolation: // auto | isolate
+ return valueID == CSSValueAuto || valueID == CSSValueIsolate;
+#endif
+ case CSSPropertyListStylePosition: // inside | outside
+ return valueID == CSSValueInside || valueID == CSSValueOutside;
+ case CSSPropertyListStyleType:
+ // See section CSS_PROP_LIST_STYLE_TYPE of file CSSValueKeywords.in
+ // for the list of supported list-style-types.
+ return (valueID >= CSSValueDisc && valueID <= CSSValueKatakanaIroha) || valueID == CSSValueNone;
+ case CSSPropertyMaskType:
+ return valueID == CSSValueLuminance || valueID == CSSValueAlpha;
+ case CSSPropertyObjectFit:
+ return valueID == CSSValueFill || valueID == CSSValueContain || valueID == CSSValueCover || valueID == CSSValueNone || valueID == CSSValueScaleDown;
+ case CSSPropertyOutlineStyle: // (<border-style> except hidden) | auto
+ return valueID == CSSValueAuto || valueID == CSSValueNone || (valueID >= CSSValueInset && valueID <= CSSValueDouble);
+ // FIXME-NEWPARSER: Support?
+ // case CSSPropertyOverflowAnchor:
+ // return valueID == CSSValueVisible || valueID == CSSValueNone || valueID == CSSValueAuto;
+ case CSSPropertyOverflowWrap: // normal | break-word
+ case CSSPropertyWordWrap:
+ return valueID == CSSValueNormal || valueID == CSSValueBreakWord;
+ case CSSPropertyOverflowX: // visible | hidden | scroll | auto | overlay
+ return valueID == CSSValueVisible || valueID == CSSValueHidden || valueID == CSSValueScroll || valueID == CSSValueAuto || valueID == CSSValueOverlay;
+ case CSSPropertyOverflowY: // visible | hidden | scroll | auto | overlay | -webkit-paged-x | -webkit-paged-y
+ return valueID == CSSValueVisible || valueID == CSSValueHidden || valueID == CSSValueScroll || valueID == CSSValueAuto || valueID == CSSValueOverlay || valueID == CSSValueWebkitPagedX || valueID == CSSValueWebkitPagedY;
+ case CSSPropertyBreakAfter:
+ case CSSPropertyBreakBefore:
+ return valueID == CSSValueAuto || valueID == CSSValueAvoid || valueID == CSSValueAvoidPage || valueID == CSSValuePage || valueID == CSSValueLeft || valueID == CSSValueRight || valueID == CSSValueRecto || valueID == CSSValueVerso || valueID == CSSValueAvoidColumn || valueID == CSSValueColumn
+#if ENABLE(CSS_REGIONS)
+ || valueID == CSSValueRegion || valueID == CSSValueAvoidRegion
+#endif
+ ;
+ case CSSPropertyBreakInside:
+ return valueID == CSSValueAuto || valueID == CSSValueAvoid || valueID == CSSValueAvoidPage || valueID == CSSValueAvoidColumn
+#if ENABLE(CSS_REGIONS)
+ || valueID == CSSValueAvoidRegion
+#endif
+ ;
+ case CSSPropertyPointerEvents:
+ // none | visiblePainted | visibleFill | visibleStroke | visible |
+ // painted | fill | stroke | auto | all | bounding-box
+ return valueID == CSSValueVisible || valueID == CSSValueNone || valueID == CSSValueAll || valueID == CSSValueAuto || (valueID >= CSSValueVisiblePainted && valueID <= CSSValueStroke);
+ case CSSPropertyPosition: // static | relative | absolute | fixed | sticky
+ return valueID == CSSValueStatic || valueID == CSSValueRelative || valueID == CSSValueAbsolute || valueID == CSSValueFixed || valueID == CSSValueWebkitSticky;
+ case CSSPropertyResize: // none | both | horizontal | vertical | auto
+ return valueID == CSSValueNone || valueID == CSSValueBoth || valueID == CSSValueHorizontal || valueID == CSSValueVertical || valueID == CSSValueAuto;
+ // FIXME-NEWPARSER: Investigate this property.
+ // case CSSPropertyScrollBehavior: // auto | smooth
+ // ASSERT(RuntimeEnabledFeatures::cssomSmoothScrollEnabled());
+ // return valueID == CSSValueAuto || valueID == CSSValueSmooth;
+ case CSSPropertyShapeRendering:
+ return valueID == CSSValueAuto || valueID == CSSValueOptimizeSpeed || valueID == CSSValueCrispedges || valueID == CSSValueGeometricPrecision;
+ case CSSPropertySpeak: // none | normal | spell-out | digits | literal-punctuation | no-punctuation
+ return valueID == CSSValueNone || valueID == CSSValueNormal || valueID == CSSValueSpellOut || valueID == CSSValueDigits || valueID == CSSValueLiteralPunctuation || valueID == CSSValueNoPunctuation;
+ case CSSPropertyStrokeLinejoin:
+ return valueID == CSSValueMiter || valueID == CSSValueRound || valueID == CSSValueBevel;
+ case CSSPropertyStrokeLinecap:
+ return valueID == CSSValueButt || valueID == CSSValueRound || valueID == CSSValueSquare;
+ case CSSPropertyTableLayout: // auto | fixed
+ return valueID == CSSValueAuto || valueID == CSSValueFixed;
+ case CSSPropertyTextAlign:
+ return (valueID >= CSSValueWebkitAuto && valueID <= CSSValueWebkitMatchParent) || valueID == CSSValueStart || valueID == CSSValueEnd;
+#if ENABLE(CSS3_TEXT)
+ case CSSPropertyWebkitTextAlignLast:
+ // auto | start | end | left | right | center | justify
+ return (valueID >= CSSValueLeft && valueID <= CSSValueJustify) || valueID == CSSValueStart || valueID == CSSValueEnd || valueID == CSSValueAuto;
+#endif
+ case CSSPropertyTextAnchor:
+ return valueID == CSSValueStart || valueID == CSSValueMiddle || valueID == CSSValueEnd;
+// FIXME-NEWPARSER: Support
+// case CSSPropertyTextCombineUpright:
+// return valueID == CSSValueNone || valueID == CSSValueAll;
+ case CSSPropertyWebkitTextDecorationStyle:
+ // solid | double | dotted | dashed | wavy
+ return valueID == CSSValueSolid || valueID == CSSValueDouble || valueID == CSSValueDotted || valueID == CSSValueDashed || valueID == CSSValueWavy;
+#if ENABLE(CSS3_TEXT)
+ case CSSPropertyWebkitTextJustify:
+ // auto | none | inter-word | distribute
+ return valueID == CSSValueInterWord || valueID == CSSValueDistribute || valueID == CSSValueAuto || valueID == CSSValueNone;
+#endif
+ case CSSPropertyWebkitTextOrientation: // mixed | upright | sideways | sideways-right
+ return valueID == CSSValueMixed || valueID == CSSValueUpright || valueID == CSSValueSideways || valueID == CSSValueSidewaysRight;
+ case CSSPropertyTextOverflow: // clip | ellipsis
+ return valueID == CSSValueClip || valueID == CSSValueEllipsis;
+ case CSSPropertyTextRendering: // auto | optimizeSpeed | optimizeLegibility | geometricPrecision
+ return valueID == CSSValueAuto || valueID == CSSValueOptimizeSpeed || valueID == CSSValueOptimizeLegibility || valueID == CSSValueGeometricPrecision;
+ case CSSPropertyTextTransform: // capitalize | uppercase | lowercase | none
+ return (valueID >= CSSValueCapitalize && valueID <= CSSValueLowercase) || valueID == CSSValueNone;
+ case CSSPropertyUnicodeBidi:
+ return valueID == CSSValueNormal || valueID == CSSValueEmbed
+ || valueID == CSSValueBidiOverride || valueID == CSSValueWebkitIsolate
+ || valueID == CSSValueWebkitIsolateOverride || valueID == CSSValueWebkitPlaintext;
+ case CSSPropertyVectorEffect:
+ return valueID == CSSValueNone || valueID == CSSValueNonScalingStroke;
+ case CSSPropertyVisibility: // visible | hidden | collapse
+ return valueID == CSSValueVisible || valueID == CSSValueHidden || valueID == CSSValueCollapse;
+ case CSSPropertyWebkitAppearance:
+ return (valueID >= CSSValueCheckbox && valueID <= CSSValueCapsLockIndicator) || valueID == CSSValueNone;
+ case CSSPropertyWebkitBackfaceVisibility:
+ return valueID == CSSValueVisible || valueID == CSSValueHidden;
+#if ENABLE(CSS_COMPOSITING)
+ case CSSPropertyMixBlendMode:
+ return valueID == CSSValueNormal || valueID == CSSValueMultiply || valueID == CSSValueScreen || valueID == CSSValueOverlay
+ || valueID == CSSValueDarken || valueID == CSSValueLighten || valueID == CSSValueColorDodge || valueID == CSSValueColorBurn
+ || valueID == CSSValueHardLight || valueID == CSSValueSoftLight || valueID == CSSValueDifference || valueID == CSSValueExclusion
+ || valueID == CSSValueHue || valueID == CSSValueSaturation || valueID == CSSValueColor || valueID == CSSValueLuminosity || valueID == CSSValuePlusDarker || valueID == CSSValuePlusLighter;
+#endif
+ case CSSPropertyWebkitBoxAlign:
+ return valueID == CSSValueStretch || valueID == CSSValueStart || valueID == CSSValueEnd || valueID == CSSValueCenter || valueID == CSSValueBaseline;
+#if ENABLE(CSS_BOX_DECORATION_BREAK)
+ case CSSPropertyWebkitBoxDecorationBreak:
+ return valueID == CSSValueClone || valueID == CSSValueSlice;
+ case CSSPropertyWebkitBoxDirection:
+ return valueID == CSSValueNormal || valueID == CSSValueReverse;
+#endif
+ case CSSPropertyWebkitBoxLines:
+ return valueID == CSSValueSingle || valueID == CSSValueMultiple;
+ case CSSPropertyWebkitBoxOrient:
+ return valueID == CSSValueHorizontal || valueID == CSSValueVertical || valueID == CSSValueInlineAxis || valueID == CSSValueBlockAxis;
+ case CSSPropertyWebkitBoxPack:
+ return valueID == CSSValueStart || valueID == CSSValueEnd || valueID == CSSValueCenter || valueID == CSSValueJustify;
+#if ENABLE(CURSOR_VISIBILITY)
+ case CSSPropertyWebkitCursorVisibility:
+ return valueID == CSSValueAuto || valueID == CSSValueAutoHide;
+#endif
+ case CSSPropertyColumnFill:
+ return valueID == CSSValueAuto || valueID == CSSValueBalance;
+ case CSSPropertyWebkitColumnAxis:
+ return valueID == CSSValueHorizontal || valueID == CSSValueVertical || valueID == CSSValueAuto;
+ case CSSPropertyWebkitColumnProgression:
+ return valueID == CSSValueNormal || valueID == CSSValueReverse;
+ case CSSPropertyAlignContent:
+ // FIXME: Per CSS alignment, this property should accept an optional <overflow-position>. We should share this parsing code with 'justify-self'.
+ return valueID == CSSValueFlexStart || valueID == CSSValueFlexEnd || valueID == CSSValueCenter || valueID == CSSValueSpaceBetween || valueID == CSSValueSpaceAround || valueID == CSSValueStretch;
+ case CSSPropertyAlignItems:
+ // FIXME: Per CSS alignment, this property should accept the same arguments as 'justify-self' so we should share its parsing code.
+ return valueID == CSSValueFlexStart || valueID == CSSValueFlexEnd || valueID == CSSValueCenter || valueID == CSSValueBaseline || valueID == CSSValueStretch;
+ case CSSPropertyAlignSelf:
+ // FIXME: Per CSS alignment, this property should accept the same arguments as 'justify-self' so we should share its parsing code.
+ return valueID == CSSValueAuto || valueID == CSSValueFlexStart || valueID == CSSValueFlexEnd || valueID == CSSValueCenter || valueID == CSSValueBaseline || valueID == CSSValueStretch;
+ case CSSPropertyFlexDirection:
+ return valueID == CSSValueRow || valueID == CSSValueRowReverse || valueID == CSSValueColumn || valueID == CSSValueColumnReverse;
+ case CSSPropertyFlexWrap:
+ return valueID == CSSValueNowrap || valueID == CSSValueWrap || valueID == CSSValueWrapReverse;
+ case CSSPropertyWebkitHyphens:
+ return valueID == CSSValueAuto || valueID == CSSValueNone || valueID == CSSValueManual;
+ case CSSPropertyJustifyContent:
+ // FIXME: Per CSS alignment, this property should accept an optional <overflow-position>. We should share this parsing code with 'justify-self'.
+ return valueID == CSSValueFlexStart || valueID == CSSValueFlexEnd || valueID == CSSValueCenter || valueID == CSSValueSpaceBetween || valueID == CSSValueSpaceAround;
+ case CSSPropertyWebkitFontKerning:
+ return valueID == CSSValueAuto || valueID == CSSValueNormal || valueID == CSSValueNone;
+ case CSSPropertyWebkitFontSmoothing:
+ return valueID == CSSValueAuto || valueID == CSSValueNone || valueID == CSSValueAntialiased || valueID == CSSValueSubpixelAntialiased;
+ case CSSPropertyWebkitLineAlign:
+ return valueID == CSSValueNone || valueID == CSSValueEdges;
+ case CSSPropertyWebkitLineBreak: // auto | loose | normal | strict | after-white-space
+ return valueID == CSSValueAuto || valueID == CSSValueLoose || valueID == CSSValueNormal || valueID == CSSValueStrict || valueID == CSSValueAfterWhiteSpace;
+ case CSSPropertyWebkitLineSnap:
+ return valueID == CSSValueNone || valueID == CSSValueBaseline || valueID == CSSValueContain;
+ case CSSPropertyWebkitMarginAfterCollapse:
+ case CSSPropertyWebkitMarginBeforeCollapse:
+ case CSSPropertyWebkitMarginBottomCollapse:
+ case CSSPropertyWebkitMarginTopCollapse:
+ return valueID == CSSValueCollapse || valueID == CSSValueSeparate || valueID == CSSValueDiscard;
+ case CSSPropertyWebkitPrintColorAdjust:
+ return valueID == CSSValueExact || valueID == CSSValueEconomy;
+ case CSSPropertyWebkitRtlOrdering:
+ return valueID == CSSValueLogical || valueID == CSSValueVisual;
+ case CSSPropertyWebkitRubyPosition:
+ return valueID == CSSValueBefore || valueID == CSSValueAfter || valueID == CSSValueInterCharacter;
+ case CSSPropertyWebkitTextCombine:
+ return valueID == CSSValueNone || valueID == CSSValueHorizontal;
+ case CSSPropertyWebkitTextSecurity: // disc | circle | square | none
+ return valueID == CSSValueDisc || valueID == CSSValueCircle || valueID == CSSValueSquare || valueID == CSSValueNone;
+ case CSSPropertyTransformStyle:
+ case CSSPropertyWebkitTransformStyle:
+ return valueID == CSSValueFlat || valueID == CSSValuePreserve3d;
+ case CSSPropertyWebkitUserDrag: // auto | none | element
+ return valueID == CSSValueAuto || valueID == CSSValueNone || valueID == CSSValueElement;
+ case CSSPropertyWebkitUserModify: // read-only | read-write
+ return valueID == CSSValueReadOnly || valueID == CSSValueReadWrite || valueID == CSSValueReadWritePlaintextOnly;
+ case CSSPropertyWebkitUserSelect: // auto | none | text | all
+ return valueID == CSSValueAuto || valueID == CSSValueNone || valueID == CSSValueText || valueID == CSSValueAll;
+ case CSSPropertyWritingMode:
+ // Note that horizontal-bt is not supported by the unprefixed version of
+ // the property, only by the -webkit- version.
+ return (valueID >= CSSValueHorizontalTb && valueID <= CSSValueHorizontalBt)
+ || valueID == CSSValueLrTb || valueID == CSSValueRlTb || valueID == CSSValueTbRl
+ || valueID == CSSValueLr || valueID == CSSValueRl || valueID == CSSValueTb;
+ case CSSPropertyWhiteSpace: // normal | pre | nowrap
+ return valueID == CSSValueNormal || valueID == CSSValuePre || valueID == CSSValuePreWrap || valueID == CSSValuePreLine || valueID == CSSValueNowrap;
+ case CSSPropertyWordBreak: // normal | break-all | keep-all | break-word (this is a custom extension)
+ return valueID == CSSValueNormal || valueID == CSSValueBreakAll || valueID == CSSValueKeepAll || valueID == CSSValueBreakWord;
+ case CSSPropertyWebkitBorderFit:
+ return valueID == CSSValueBorder || valueID == CSSValueLines;
+#if ENABLE(CSS_REGIONS)
+ case CSSPropertyWebkitRegionFragment:
+ return valueID == CSSValueAuto || valueID == CSSValueBreak;
+#endif
+#if ENABLE(TOUCH_EVENTS)
+ case CSSPropertyTouchAction: // auto | manipulation
+ return valueID == CSSValueAuto || valueID == CSSValueManipulation;
+#endif
+#if ENABLE(CSS_TRAILING_WORD)
+ case CSSPropertyAppleTrailingWord: // auto | -apple-partially-balanced
+ return valueID == CSSValueAuto || valueID == CSSValueWebkitPartiallyBalanced;
+#endif
+#if ENABLE(APPLE_PAY)
+ case CSSPropertyApplePayButtonStyle: // white | white-outline | black
+ return valueID == CSSValueWhite || valueID == CSSValueWhiteOutline || valueID == CSSValueBlack;
+ case CSSPropertyApplePayButtonType: // plain | buy | set-up | donate
+ return valueID == CSSValuePlain || valueID == CSSValueBuy || valueID == CSSValueSetUp || valueID == CSSValueDonate;
+#endif
+ case CSSPropertyWebkitNbspMode: // normal | space
+ return valueID == CSSValueNormal || valueID == CSSValueSpace;
+ case CSSPropertyWebkitTextZoom:
+ return valueID == CSSValueNormal || valueID == CSSValueReset;
+#if PLATFORM(IOS)
+ // Apple specific property. These will never be standardized and is purely to
+ // support custom WebKit-based Apple applications.
+ case CSSPropertyWebkitTouchCallout:
+ return valueID == CSSValueDefault || valueID == CSSValueNone;
+#endif
+ case CSSPropertyWebkitMarqueeDirection:
+ return valueID == CSSValueForwards || valueID == CSSValueBackwards || valueID == CSSValueAhead || valueID == CSSValueReverse || valueID == CSSValueLeft || valueID == CSSValueRight || valueID == CSSValueDown
+ || valueID == CSSValueUp || valueID == CSSValueAuto;
+ case CSSPropertyWebkitMarqueeStyle:
+ return valueID == CSSValueNone || valueID == CSSValueSlide || valueID == CSSValueScroll || valueID == CSSValueAlternate;
+ case CSSPropertyFontVariantPosition: // normal | sub | super
+ return valueID == CSSValueNormal || valueID == CSSValueSub || valueID == CSSValueSuper;
+ case CSSPropertyFontVariantCaps: // normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps
+ return valueID == CSSValueNormal || valueID == CSSValueSmallCaps || valueID == CSSValueAllSmallCaps || valueID == CSSValuePetiteCaps || valueID == CSSValueAllPetiteCaps || valueID == CSSValueUnicase || valueID == CSSValueTitlingCaps;
+ case CSSPropertyFontVariantAlternates: // We only support the normal and historical-forms values.
+ return valueID == CSSValueNormal || valueID == CSSValueHistoricalForms;
+#if ENABLE(ACCELERATED_OVERFLOW_SCROLLING)
+ case CSSPropertyWebkitOverflowScrolling:
+ return valueID == CSSValueAuto || valueID == CSSValueTouch;
+#endif
+ default:
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+}
+
+bool CSSParserFastPaths::isKeywordPropertyID(CSSPropertyID propertyId)
+{
+ switch (propertyId) {
+ case CSSPropertyBorderBottomStyle:
+ case CSSPropertyBorderCollapse:
+ case CSSPropertyBorderLeftStyle:
+ case CSSPropertyBorderRightStyle:
+ case CSSPropertyBorderTopStyle:
+ case CSSPropertyBoxSizing:
+ case CSSPropertyBreakAfter:
+ case CSSPropertyBreakBefore:
+ case CSSPropertyBreakInside:
+ case CSSPropertyCaptionSide:
+ case CSSPropertyClear:
+ case CSSPropertyColumnFill:
+ case CSSPropertyWebkitColumnProgression:
+ case CSSPropertyColumnRuleStyle:
+ case CSSPropertyDirection:
+ case CSSPropertyDisplay:
+ case CSSPropertyEmptyCells:
+ case CSSPropertyFlexDirection:
+ case CSSPropertyFlexWrap:
+ case CSSPropertyFloat:
+ case CSSPropertyFontStretch:
+ case CSSPropertyFontStyle:
+ case CSSPropertyFontVariantAlternates:
+ case CSSPropertyFontVariantCaps:
+ case CSSPropertyFontVariantPosition:
+ case CSSPropertyImageRendering:
+ case CSSPropertyListStylePosition:
+ case CSSPropertyListStyleType:
+ case CSSPropertyObjectFit:
+ case CSSPropertyOutlineStyle:
+ case CSSPropertyOverflowWrap:
+ case CSSPropertyOverflowX:
+ case CSSPropertyOverflowY:
+ case CSSPropertyPointerEvents:
+ case CSSPropertyPosition:
+ case CSSPropertyResize:
+ case CSSPropertySpeak:
+ case CSSPropertyTableLayout:
+ case CSSPropertyTextAlign:
+ case CSSPropertyTextLineThroughMode:
+ case CSSPropertyTextLineThroughStyle:
+ case CSSPropertyTextOverflow:
+ case CSSPropertyTextOverlineMode:
+ case CSSPropertyTextOverlineStyle:
+ case CSSPropertyTextRendering:
+ case CSSPropertyTextTransform:
+ case CSSPropertyTextUnderlineMode:
+ case CSSPropertyTextUnderlineStyle:
+ case CSSPropertyTransformStyle:
+ case CSSPropertyUnicodeBidi:
+ case CSSPropertyVisibility:
+ case CSSPropertyWebkitAppearance:
+ case CSSPropertyWebkitBackfaceVisibility:
+ case CSSPropertyWebkitBorderAfterStyle:
+ case CSSPropertyWebkitBorderBeforeStyle:
+ case CSSPropertyWebkitBorderEndStyle:
+ case CSSPropertyWebkitBorderFit:
+ case CSSPropertyWebkitBorderStartStyle:
+ case CSSPropertyWebkitBoxAlign:
+ case CSSPropertyWebkitBoxDirection:
+ case CSSPropertyWebkitBoxLines:
+ case CSSPropertyWebkitBoxOrient:
+ case CSSPropertyWebkitBoxPack:
+ case CSSPropertyWebkitColumnAxis:
+ case CSSPropertyWebkitFontKerning:
+ case CSSPropertyWebkitFontSmoothing:
+ case CSSPropertyWebkitHyphens:
+ case CSSPropertyWebkitLineAlign:
+ case CSSPropertyWebkitLineBreak:
+ case CSSPropertyWebkitLineSnap:
+ case CSSPropertyWebkitMarginAfterCollapse:
+ case CSSPropertyWebkitMarginBeforeCollapse:
+ case CSSPropertyWebkitMarginBottomCollapse:
+ case CSSPropertyWebkitMarginTopCollapse:
+ case CSSPropertyWebkitMarqueeDirection:
+ case CSSPropertyWebkitMarqueeStyle:
+ case CSSPropertyWebkitNbspMode:
+ case CSSPropertyWebkitPrintColorAdjust:
+ case CSSPropertyWebkitRtlOrdering:
+ case CSSPropertyWebkitRubyPosition:
+ case CSSPropertyWebkitTextCombine:
+ case CSSPropertyWebkitTextDecorationStyle:
+ case CSSPropertyWebkitTextOrientation:
+ case CSSPropertyWebkitTextSecurity:
+ case CSSPropertyWebkitTextZoom:
+ case CSSPropertyWebkitTransformStyle:
+ case CSSPropertyWebkitUserDrag:
+ case CSSPropertyWebkitUserModify:
+ case CSSPropertyWebkitUserSelect:
+ case CSSPropertyWhiteSpace:
+ case CSSPropertyWordBreak:
+ case CSSPropertyWordWrap:
+
+ // SVG CSS properties from SVG 1.1, Appendix N: Property Index.
+ case CSSPropertyAlignmentBaseline:
+ case CSSPropertyBufferedRendering:
+ case CSSPropertyClipRule:
+ case CSSPropertyColorInterpolation:
+ case CSSPropertyColorInterpolationFilters:
+ case CSSPropertyColorRendering:
+ case CSSPropertyDominantBaseline:
+ case CSSPropertyFillRule:
+ case CSSPropertyMaskType:
+ case CSSPropertyShapeRendering:
+ case CSSPropertyStrokeLinecap:
+ case CSSPropertyStrokeLinejoin:
+ case CSSPropertyTextAnchor:
+ case CSSPropertyVectorEffect:
+ case CSSPropertyWritingMode:
+
+ // FIXME-NEWPARSER: Treat all as a keyword property.
+ // case CSSPropertyAll:
+
+ // FIXME-NEWPARSER: Add the following unprefixed properties:
+ // case CSSPropertyBackfaceVisibility:
+ // case CSSPropertyFontKerning:
+ // case CSSPropertyHyphens:
+ // case CSSPropertyOverflowAnchor:
+ // case CSSPropertyScrollBehavior:
+ // case CSSPropertyScrollSnapType:
+ // case CSSPropertyTextAlignLast:
+ // case CSSPropertyTextCombineUpright:
+ // case CSSPropertyTextDecorationStyle:
+ // case CSSPropertyTextJustify:
+ // case CSSPropertyTextOrientation:
+ // case CSSPropertyUserSelect:
+#if ENABLE(CSS_TRAILING_WORD)
+ case CSSPropertyAppleTrailingWord:
+#endif
+#if ENABLE(CSS_COMPOSITING)
+ case CSSPropertyIsolation:
+ case CSSPropertyMixBlendMode:
+#endif
+#if ENABLE(TOUCH_EVENTS)
+ case CSSPropertyTouchAction:
+#endif
+#if ENABLE(CSS_BOX_DECORATION_BREAK)
+ case CSSPropertyWebkitBoxDecorationBreak:
+#endif
+#if ENABLE(CURSOR_VISIBILITY)
+ case CSSPropertyWebkitCursorVisibility:
+#endif
+#if ENABLE(ACCELERATED_OVERFLOW_SCROLLING)
+ case CSSPropertyWebkitOverflowScrolling:
+#endif
+#if ENABLE(CSS_REGIONS)
+ case CSSPropertyWebkitRegionFragment:
+#endif
+#if ENABLE(CSS3_TEXT)
+ case CSSPropertyWebkitTextAlignLast:
+ case CSSPropertyWebkitTextJustify:
+#endif
+#if PLATFORM(IOS)
+ // Apple specific property. This will never be standardized and is purely to
+ // support custom WebKit-based Apple applications.
+ case CSSPropertyWebkitTouchCallout:
+#endif
+#if ENABLE(APPLE_PAY)
+ case CSSPropertyApplePayButtonStyle:
+ case CSSPropertyApplePayButtonType:
+#endif
+ return true;
+ case CSSPropertyJustifyContent:
+ case CSSPropertyAlignContent:
+ case CSSPropertyAlignItems:
+ case CSSPropertyAlignSelf:
+ return !RuntimeEnabledFeatures::sharedFeatures().isCSSGridLayoutEnabled();
+ default:
+ return false;
+ }
+}
+
+static bool isUniversalKeyword(const String& string)
+{
+ // These keywords can be used for all properties.
+ return equalLettersIgnoringASCIICase(string, "initial")
+ || equalLettersIgnoringASCIICase(string, "inherit")
+ || equalLettersIgnoringASCIICase(string, "unset")
+ || equalLettersIgnoringASCIICase(string, "revert");
+}
+
+static RefPtr<CSSValue> parseKeywordValue(CSSPropertyID propertyId, const String& string, CSSParserMode parserMode)
+{
+ ASSERT(!string.isEmpty());
+
+ if (!CSSParserFastPaths::isKeywordPropertyID(propertyId)) {
+ // All properties accept the values of "initial" and "inherit".
+ if (!isUniversalKeyword(string))
+ return nullptr;
+
+ // Parse initial/inherit shorthands using the CSSPropertyParser.
+ if (shorthandForProperty(propertyId).length())
+ return nullptr;
+
+ // Descriptors do not support css wide keywords.
+ if (CSSProperty::isDescriptorOnly(propertyId))
+ return nullptr;
+ }
+
+ CSSValueID valueID = cssValueKeywordID(string);
+
+ if (!valueID)
+ return nullptr;
+
+ if (valueID == CSSValueInherit)
+ return CSSValuePool::singleton().createInheritedValue();
+ if (valueID == CSSValueInitial)
+ return CSSValuePool::singleton().createExplicitInitialValue();
+ if (valueID == CSSValueUnset)
+ return CSSValuePool::singleton().createUnsetValue();
+ if (valueID == CSSValueRevert)
+ return CSSValuePool::singleton().createRevertValue();
+
+ if (CSSParserFastPaths::isValidKeywordPropertyAndValue(propertyId, valueID, parserMode))
+ return CSSPrimitiveValue::createIdentifier(valueID);
+ return nullptr;
+}
+
+template <typename CharType>
+static bool parseTransformTranslateArguments(CharType*& pos, CharType* end, unsigned expectedCount, CSSFunctionValue* transformValue)
+{
+ while (expectedCount) {
+ size_t delimiter = WTF::find(pos, end - pos, expectedCount == 1 ? ')' : ',');
+ if (delimiter == notFound)
+ return false;
+ unsigned argumentLength = static_cast<unsigned>(delimiter);
+ CSSPrimitiveValue::UnitType unit = CSSPrimitiveValue::UnitType::CSS_NUMBER;
+ double number;
+ if (!parseSimpleLength(pos, argumentLength, unit, number))
+ return false;
+ if (!number && unit == CSSPrimitiveValue::CSS_NUMBER)
+ unit = CSSPrimitiveValue::UnitType::CSS_PX;
+ if (unit == CSSPrimitiveValue::UnitType::CSS_NUMBER || (unit == CSSPrimitiveValue::UnitType::CSS_PERCENTAGE && (transformValue->name() == CSSValueTranslateZ || (transformValue->name() == CSSValueTranslate3d && expectedCount == 1))))
+ return false;
+ transformValue->append(CSSPrimitiveValue::create(number, unit));
+ pos += argumentLength + 1;
+ --expectedCount;
+ }
+ return true;
+}
+
+template <typename CharType>
+static bool parseTransformNumberArguments(CharType*& pos, CharType* end, unsigned expectedCount, CSSFunctionValue* transformValue)
+{
+ while (expectedCount) {
+ size_t delimiter = WTF::find(pos, end - pos, expectedCount == 1 ? ')' : ',');
+ if (delimiter == notFound)
+ return false;
+ unsigned argumentLength = static_cast<unsigned>(delimiter);
+ bool ok;
+ double number = charactersToDouble(pos, argumentLength, &ok);
+ if (!ok)
+ return false;
+ transformValue->append(CSSPrimitiveValue::create(number, CSSPrimitiveValue::UnitType::CSS_NUMBER));
+ pos += argumentLength + 1;
+ --expectedCount;
+ }
+ return true;
+}
+
+static const int kShortestValidTransformStringLength = 12;
+
+template <typename CharType>
+static RefPtr<CSSFunctionValue> parseSimpleTransformValue(CharType*& pos, CharType* end)
+{
+ if (end - pos < kShortestValidTransformStringLength)
+ return nullptr;
+
+ const bool isTranslate = toASCIILower(pos[0]) == 't'
+ && toASCIILower(pos[1]) == 'r'
+ && toASCIILower(pos[2]) == 'a'
+ && toASCIILower(pos[3]) == 'n'
+ && toASCIILower(pos[4]) == 's'
+ && toASCIILower(pos[5]) == 'l'
+ && toASCIILower(pos[6]) == 'a'
+ && toASCIILower(pos[7]) == 't'
+ && toASCIILower(pos[8]) == 'e';
+
+ if (isTranslate) {
+ CSSValueID transformType;
+ unsigned expectedArgumentCount = 1;
+ unsigned argumentStart = 11;
+ CharType c9 = toASCIILower(pos[9]);
+ if (c9 == 'x' && pos[10] == '(') {
+ transformType = CSSValueTranslateX;
+ } else if (c9 == 'y' && pos[10] == '(') {
+ transformType = CSSValueTranslateY;
+ } else if (c9 == 'z' && pos[10] == '(') {
+ transformType = CSSValueTranslateZ;
+ } else if (c9 == '(') {
+ transformType = CSSValueTranslate;
+ expectedArgumentCount = 2;
+ argumentStart = 10;
+ } else if (c9 == '3' && toASCIILower(pos[10]) == 'd' && pos[11] == '(') {
+ transformType = CSSValueTranslate3d;
+ expectedArgumentCount = 3;
+ argumentStart = 12;
+ } else {
+ return nullptr;
+ }
+ pos += argumentStart;
+ RefPtr<CSSFunctionValue> transformValue = CSSFunctionValue::create(transformType);
+ if (!parseTransformTranslateArguments(pos, end, expectedArgumentCount, transformValue.get()))
+ return nullptr;
+ return transformValue;
+ }
+
+ const bool isMatrix3d = toASCIILower(pos[0]) == 'm'
+ && toASCIILower(pos[1]) == 'a'
+ && toASCIILower(pos[2]) == 't'
+ && toASCIILower(pos[3]) == 'r'
+ && toASCIILower(pos[4]) == 'i'
+ && toASCIILower(pos[5]) == 'x'
+ && pos[6] == '3'
+ && toASCIILower(pos[7]) == 'd'
+ && pos[8] == '(';
+
+ if (isMatrix3d) {
+ pos += 9;
+ RefPtr<CSSFunctionValue> transformValue = CSSFunctionValue::create(CSSValueMatrix3d);
+ if (!parseTransformNumberArguments(pos, end, 16, transformValue.get()))
+ return nullptr;
+ return transformValue;
+ }
+
+ const bool isScale3d = toASCIILower(pos[0]) == 's'
+ && toASCIILower(pos[1]) == 'c'
+ && toASCIILower(pos[2]) == 'a'
+ && toASCIILower(pos[3]) == 'l'
+ && toASCIILower(pos[4]) == 'e'
+ && pos[5] == '3'
+ && toASCIILower(pos[6]) == 'd'
+ && pos[7] == '(';
+
+ if (isScale3d) {
+ pos += 8;
+ RefPtr<CSSFunctionValue> transformValue = CSSFunctionValue::create(CSSValueScale3d);
+ if (!parseTransformNumberArguments(pos, end, 3, transformValue.get()))
+ return nullptr;
+ return transformValue;
+ }
+
+ return nullptr;
+}
+
+template <typename CharType>
+static bool transformCanLikelyUseFastPath(const CharType* chars, unsigned length)
+{
+ // Very fast scan that attempts to reject most transforms that couldn't
+ // take the fast path. This avoids doing the malloc and string->double
+ // conversions in parseSimpleTransformValue only to discard them when we
+ // run into a transform component we don't understand.
+ unsigned i = 0;
+ while (i < length) {
+ if (isCSSSpace(chars[i])) {
+ ++i;
+ continue;
+ }
+ if (length - i < kShortestValidTransformStringLength)
+ return false;
+ switch (toASCIILower(chars[i])) {
+ case 't':
+ // translate, translateX, translateY, translateZ, translate3d.
+ if (toASCIILower(chars[i + 8]) != 'e')
+ return false;
+ i += 9;
+ break;
+ case 'm':
+ // matrix3d.
+ if (toASCIILower(chars[i + 7]) != 'd')
+ return false;
+ i += 8;
+ break;
+ case 's':
+ // scale3d.
+ if (toASCIILower(chars[i + 6]) != 'd')
+ return false;
+ i += 7;
+ break;
+ default:
+ // All other things, ex. rotate.
+ return false;
+ }
+ size_t argumentsEnd = WTF::find(chars, length, ')', i);
+ if (argumentsEnd == notFound)
+ return false;
+ // Advance to the end of the arguments.
+ i = argumentsEnd + 1;
+ }
+ return i == length;
+}
+
+template <typename CharType>
+static RefPtr<CSSValueList> parseSimpleTransformList(const CharType* chars, unsigned length)
+{
+ if (!transformCanLikelyUseFastPath(chars, length))
+ return nullptr;
+ const CharType*& pos = chars;
+ const CharType* end = chars + length;
+ RefPtr<CSSValueList> transformList;
+ while (pos < end) {
+ while (pos < end && isCSSSpace(*pos))
+ ++pos;
+ if (pos >= end)
+ break;
+ RefPtr<CSSFunctionValue> transformValue = parseSimpleTransformValue(pos, end);
+ if (!transformValue)
+ return nullptr;
+ if (!transformList)
+ transformList = CSSValueList::createSpaceSeparated();
+ transformList->append(*transformValue);
+ }
+ return transformList;
+}
+
+static RefPtr<CSSValue> parseSimpleTransform(CSSPropertyID propertyID, const String& string)
+{
+ ASSERT(!string.isEmpty());
+
+ if (propertyID != CSSPropertyTransform)
+ return nullptr;
+ if (string.is8Bit())
+ return parseSimpleTransformList(string.characters8(), string.length());
+ return parseSimpleTransformList(string.characters16(), string.length());
+}
+
+RefPtr<CSSValue> CSSParserFastPaths::maybeParseValue(CSSPropertyID propertyID, const String& string, CSSParserMode parserMode)
+{
+ RefPtr<CSSValue> result = parseSimpleLengthValue(propertyID, string, parserMode);
+ if (result)
+ return result;
+ if (isColorPropertyID(propertyID))
+ return parseColor(string, parserMode);
+ result = parseKeywordValue(propertyID, string, parserMode);
+ if (result)
+ return result;
+ result = parseSimpleTransform(propertyID, string);
+ if (result)
+ return result;
+ return nullptr;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserFastPaths.h b/Source/WebCore/css/parser/CSSParserFastPaths.h
new file mode 100644
index 000000000..35fbf6a34
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserFastPaths.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserMode.h"
+#include "CSSPropertyNames.h"
+#include "CSSValueKeywords.h"
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CSSValue;
+class StyleSheetContents;
+
+class CSSParserFastPaths {
+public:
+ // Parses simple values like '10px' or 'green', but makes no guarantees
+ // about handling any property completely.
+ static RefPtr<CSSValue> maybeParseValue(CSSPropertyID, const String&, CSSParserMode);
+
+ // Properties handled here shouldn't be explicitly handled in CSSPropertyParser
+ static bool isKeywordPropertyID(CSSPropertyID);
+ static bool isValidKeywordPropertyAndValue(CSSPropertyID, CSSValueID, CSSParserMode);
+
+ static RefPtr<CSSValue> parseColor(const String&, CSSParserMode);
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserIdioms.cpp b/Source/WebCore/css/parser/CSSParserIdioms.cpp
new file mode 100644
index 000000000..a7ae2084d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserIdioms.cpp
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSParserIdioms.h"
+#include "CSSValueKeywords.h"
+#include "TextEncoding.h"
+
+namespace WebCore {
+
+bool isValueAllowedInMode(unsigned short id, CSSParserMode mode)
+{
+ switch (id) {
+ case CSSValueInternalVariableValue:
+ return isUASheetBehavior(mode);
+ case CSSValueWebkitFocusRingColor:
+ return isUASheetBehavior(mode) || isQuirksModeBehavior(mode);
+ default:
+ return true;
+ }
+}
+
+URL completeURL(const CSSParserContext& context, const String& url)
+{
+ if (url.isNull())
+ return URL();
+ if (context.charset.isEmpty())
+ return URL(context.baseURL, url);
+ return URL(context.baseURL, url, context.charset);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserIdioms.h b/Source/WebCore/css/parser/CSSParserIdioms.h
new file mode 100644
index 000000000..fe8b54f5b
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserIdioms.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CSSParserMode.h"
+#include <wtf/ASCIICType.h>
+#include <wtf/text/StringView.h>
+
+namespace WebCore {
+
+class URL;
+
+// Space characters as defined by the CSS specification.
+// http://www.w3.org/TR/css3-syntax/#whitespace
+inline bool isCSSSpace(UChar c)
+{
+ return c == ' ' || c == '\t' || c == '\n';
+}
+
+// http://dev.w3.org/csswg/css-syntax/#name-start-code-point
+template <typename CharacterType>
+bool isNameStartCodePoint(CharacterType c)
+{
+ return isASCIIAlpha(c) || c == '_' || !isASCII(c);
+}
+
+// http://dev.w3.org/csswg/css-syntax/#name-code-point
+template <typename CharacterType>
+bool isNameCodePoint(CharacterType c)
+{
+ return isNameStartCodePoint(c) || isASCIIDigit(c) || c == '-';
+}
+
+bool isValueAllowedInMode(unsigned short, CSSParserMode);
+
+URL completeURL(const CSSParserContext&, const String& url);
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserImpl.cpp b/Source/WebCore/css/parser/CSSParserImpl.cpp
new file mode 100644
index 000000000..2da1144d9
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserImpl.cpp
@@ -0,0 +1,910 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSParserImpl.h"
+
+#include "CSSAtRuleID.h"
+#include "CSSCustomPropertyValue.h"
+#include "CSSDeferredParser.h"
+#include "CSSKeyframeRule.h"
+#include "CSSKeyframesRule.h"
+#include "CSSParserObserver.h"
+#include "CSSParserObserverWrapper.h"
+#include "CSSParserSelector.h"
+#include "CSSPropertyParser.h"
+#include "CSSSelectorParser.h"
+#include "CSSStyleSheet.h"
+#include "CSSSupportsParser.h"
+#include "CSSTokenizer.h"
+#include "CSSVariableParser.h"
+#include "Document.h"
+#include "Element.h"
+#include "MediaQueryParser.h"
+#include "StyleProperties.h"
+#include "StyleRuleImport.h"
+#include "StyleSheetContents.h"
+
+#include <bitset>
+#include <memory>
+
+namespace WebCore {
+
+CSSParserImpl::CSSParserImpl(const CSSParserContext& context, StyleSheetContents* styleSheet)
+ : m_context(context)
+ , m_styleSheet(styleSheet)
+{
+
+}
+
+CSSParserImpl::CSSParserImpl(CSSDeferredParser& deferredParser)
+ : m_context(deferredParser.context())
+ , m_styleSheet(deferredParser.styleSheet())
+ , m_deferredParser(&deferredParser)
+{
+}
+
+CSSParserImpl::CSSParserImpl(const CSSParserContext& context, const String& string, StyleSheetContents* styleSheet, CSSParserObserverWrapper* wrapper, CSSParser::RuleParsing ruleParsing)
+ : m_context(context)
+ , m_styleSheet(styleSheet)
+ , m_observerWrapper(wrapper)
+{
+ m_tokenizer = wrapper ? std::make_unique<CSSTokenizer>(string, *wrapper) : std::make_unique<CSSTokenizer>(string);
+ if (context.deferredCSSParserEnabled && !wrapper && styleSheet && ruleParsing == CSSParser::RuleParsing::Deferred)
+ m_deferredParser = CSSDeferredParser::create(context, string, *styleSheet);
+}
+
+CSSParser::ParseResult CSSParserImpl::parseValue(MutableStyleProperties* declaration, CSSPropertyID propertyID, const String& string, bool important, const CSSParserContext& context)
+{
+ CSSParserImpl parser(context, string);
+ StyleRule::Type ruleType = StyleRule::Style;
+#if ENABLE(CSS_DEVICE_ADAPTATION)
+ if (declaration->cssParserMode() == CSSViewportRuleMode)
+ ruleType = StyleRule::Viewport;
+#endif
+ parser.consumeDeclarationValue(parser.tokenizer()->tokenRange(), propertyID, important, ruleType);
+ if (parser.m_parsedProperties.isEmpty())
+ return CSSParser::ParseResult::Error;
+ return declaration->addParsedProperties(parser.m_parsedProperties) ? CSSParser::ParseResult::Changed : CSSParser::ParseResult::Unchanged;
+}
+
+CSSParser::ParseResult CSSParserImpl::parseCustomPropertyValue(MutableStyleProperties* declaration, const AtomicString& propertyName, const String& string, bool important, const CSSParserContext& context)
+{
+ CSSParserImpl parser(context, string);
+ parser.consumeCustomPropertyValue(parser.tokenizer()->tokenRange(), propertyName, important);
+ if (parser.m_parsedProperties.isEmpty())
+ return CSSParser::ParseResult::Error;
+ return declaration->addParsedProperties(parser.m_parsedProperties) ? CSSParser::ParseResult::Changed : CSSParser::ParseResult::Unchanged;
+}
+
+static inline void filterProperties(bool important, const ParsedPropertyVector& input, ParsedPropertyVector& output, size_t& unusedEntries, std::bitset<numCSSProperties>& seenProperties, HashSet<AtomicString>& seenCustomProperties)
+{
+ // Add properties in reverse order so that highest priority definitions are reached first. Duplicate definitions can then be ignored when found.
+ for (size_t i = input.size(); i--; ) {
+ const CSSProperty& property = input[i];
+ if (property.isImportant() != important)
+ continue;
+ const unsigned propertyIDIndex = property.id() - firstCSSProperty;
+
+ if (property.id() == CSSPropertyCustom) {
+ auto& name = downcast<CSSCustomPropertyValue>(*property.value()).name();
+ if (!seenCustomProperties.add(name).isNewEntry)
+ continue;
+ output[--unusedEntries] = property;
+ continue;
+ }
+
+ // FIXME-NEWPARSER: We won't support @apply yet.
+ /*else if (property.id() == CSSPropertyApplyAtRule) {
+ // FIXME: Do we need to do anything here?
+ } */
+
+ if (seenProperties.test(propertyIDIndex))
+ continue;
+ seenProperties.set(propertyIDIndex);
+
+ output[--unusedEntries] = property;
+ }
+}
+
+Ref<DeferredStyleProperties> CSSParserImpl::createDeferredStyleProperties(const CSSParserTokenRange& propertyRange)
+{
+ ASSERT(m_deferredParser);
+ return DeferredStyleProperties::create(propertyRange, *m_deferredParser);
+}
+
+static Ref<ImmutableStyleProperties> createStyleProperties(ParsedPropertyVector& parsedProperties, CSSParserMode mode)
+{
+ std::bitset<numCSSProperties> seenProperties;
+ size_t unusedEntries = parsedProperties.size();
+ ParsedPropertyVector results(unusedEntries);
+ HashSet<AtomicString> seenCustomProperties;
+
+ filterProperties(true, parsedProperties, results, unusedEntries, seenProperties, seenCustomProperties);
+ filterProperties(false, parsedProperties, results, unusedEntries, seenProperties, seenCustomProperties);
+
+ Ref<ImmutableStyleProperties> result = ImmutableStyleProperties::create(results.data() + unusedEntries, results.size() - unusedEntries, mode);
+ parsedProperties.clear();
+ return result;
+}
+
+Ref<ImmutableStyleProperties> CSSParserImpl::parseInlineStyleDeclaration(const String& string, Element* element)
+{
+ CSSParserContext context(element->document());
+ context.mode = strictToCSSParserMode(element->isHTMLElement() && !element->document().inQuirksMode());
+
+ CSSParserImpl parser(context, string);
+ parser.consumeDeclarationList(parser.tokenizer()->tokenRange(), StyleRule::Style);
+ return createStyleProperties(parser.m_parsedProperties, context.mode);
+}
+
+Ref<ImmutableStyleProperties> CSSParserImpl::parseDeferredDeclaration(CSSParserTokenRange tokenRange, const CSSParserContext& context, StyleSheetContents* styleSheet)
+{
+ if (!styleSheet) {
+ ParsedPropertyVector properties;
+ return createStyleProperties(properties, context.mode);
+ }
+ CSSParserImpl parser(context, styleSheet);
+ parser.consumeDeclarationList(tokenRange, StyleRule::Style);
+ return createStyleProperties(parser.m_parsedProperties, context.mode);
+}
+
+void CSSParserImpl::parseDeferredRuleList(CSSParserTokenRange tokenRange, CSSDeferredParser& deferredParser, Vector<RefPtr<StyleRuleBase>>& childRules)
+{
+ if (!deferredParser.styleSheet())
+ return;
+ CSSParserImpl parser(deferredParser);
+ parser.consumeRuleList(tokenRange, RegularRuleList, [&childRules](const RefPtr<StyleRuleBase>& rule) {
+ childRules.append(rule);
+ });
+}
+
+void CSSParserImpl::parseDeferredKeyframeList(CSSParserTokenRange tokenRange, CSSDeferredParser& deferredParser, StyleRuleKeyframes& keyframeRule)
+{
+ if (!deferredParser.styleSheet())
+ return;
+ CSSParserImpl parser(deferredParser);
+ parser.consumeRuleList(tokenRange, KeyframesRuleList, [&keyframeRule](const RefPtr<StyleRuleBase>& keyframe) {
+ keyframeRule.parserAppendKeyframe(downcast<const StyleRuleKeyframe>(keyframe.get()));
+ });
+}
+
+bool CSSParserImpl::parseDeclarationList(MutableStyleProperties* declaration, const String& string, const CSSParserContext& context)
+{
+ CSSParserImpl parser(context, string);
+ StyleRule::Type ruleType = StyleRule::Style;
+#if ENABLE(CSS_DEVICE_ADAPTATION)
+ if (declaration->cssParserMode() == CSSViewportRuleMode)
+ ruleType = StyleRule::Viewport;
+#endif
+ parser.consumeDeclarationList(parser.tokenizer()->tokenRange(), ruleType);
+ if (parser.m_parsedProperties.isEmpty())
+ return false;
+
+ std::bitset<numCSSProperties> seenProperties;
+ size_t unusedEntries = parser.m_parsedProperties.size();
+ ParsedPropertyVector results(unusedEntries);
+ HashSet<AtomicString> seenCustomProperties;
+ filterProperties(true, parser.m_parsedProperties, results, unusedEntries, seenProperties, seenCustomProperties);
+ filterProperties(false, parser.m_parsedProperties, results, unusedEntries, seenProperties, seenCustomProperties);
+ if (unusedEntries)
+ results.remove(0, unusedEntries);
+ return declaration->addParsedProperties(results);
+}
+
+RefPtr<StyleRuleBase> CSSParserImpl::parseRule(const String& string, const CSSParserContext& context, StyleSheetContents* styleSheet, AllowedRulesType allowedRules)
+{
+ CSSParserImpl parser(context, string, styleSheet);
+ CSSParserTokenRange range = parser.tokenizer()->tokenRange();
+ range.consumeWhitespace();
+ if (range.atEnd())
+ return nullptr; // Parse error, empty rule
+ RefPtr<StyleRuleBase> rule;
+ if (range.peek().type() == AtKeywordToken)
+ rule = parser.consumeAtRule(range, allowedRules);
+ else
+ rule = parser.consumeQualifiedRule(range, allowedRules);
+ if (!rule)
+ return nullptr; // Parse error, failed to consume rule
+ range.consumeWhitespace();
+ if (!rule || !range.atEnd())
+ return nullptr; // Parse error, trailing garbage
+ return rule;
+}
+
+void CSSParserImpl::parseStyleSheet(const String& string, const CSSParserContext& context, StyleSheetContents* styleSheet, CSSParser::RuleParsing ruleParsing)
+{
+ CSSParserImpl parser(context, string, styleSheet, nullptr, ruleParsing);
+ bool firstRuleValid = parser.consumeRuleList(parser.tokenizer()->tokenRange(), TopLevelRuleList, [&styleSheet](RefPtr<StyleRuleBase> rule) {
+ if (rule->isCharsetRule())
+ return;
+ styleSheet->parserAppendRule(rule.releaseNonNull());
+ });
+ styleSheet->setHasSyntacticallyValidCSSHeader(firstRuleValid);
+ parser.adoptTokenizerEscapedStrings();
+}
+
+void CSSParserImpl::adoptTokenizerEscapedStrings()
+{
+ if (!m_deferredParser || !m_tokenizer)
+ return;
+ m_deferredParser->adoptTokenizerEscapedStrings(m_tokenizer->escapedStringsForAdoption());
+}
+
+CSSSelectorList CSSParserImpl::parsePageSelector(CSSParserTokenRange range, StyleSheetContents* styleSheet)
+{
+ // We only support a small subset of the css-page spec.
+ range.consumeWhitespace();
+ AtomicString typeSelector;
+ if (range.peek().type() == IdentToken)
+ typeSelector = range.consume().value().toAtomicString();
+
+ AtomicString pseudo;
+ if (range.peek().type() == ColonToken) {
+ range.consume();
+ if (range.peek().type() != IdentToken)
+ return CSSSelectorList();
+ pseudo = range.consume().value().toAtomicString();
+ }
+
+ range.consumeWhitespace();
+ if (!range.atEnd())
+ return CSSSelectorList(); // Parse error; extra tokens in @page selector
+
+ std::unique_ptr<CSSParserSelector> selector;
+ if (!typeSelector.isNull() && pseudo.isNull())
+ selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector(QualifiedName(nullAtom, typeSelector, styleSheet->defaultNamespace())));
+ else {
+ selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector);
+ if (!pseudo.isNull()) {
+ selector = std::unique_ptr<CSSParserSelector>(CSSParserSelector::parsePagePseudoSelector(pseudo));
+ if (!selector || selector->match() != CSSSelector::PagePseudoClass)
+ return CSSSelectorList();
+ }
+ if (!typeSelector.isNull())
+ selector->prependTagSelector(QualifiedName(nullAtom, typeSelector, styleSheet->defaultNamespace()));
+ }
+
+ selector->setForPage();
+ Vector<std::unique_ptr<CSSParserSelector>> selectorVector;
+ selectorVector.append(WTFMove(selector));
+ CSSSelectorList selectorList;
+ selectorList.adoptSelectorVector(selectorVector);
+ return selectorList;
+}
+
+std::unique_ptr<Vector<double>> CSSParserImpl::parseKeyframeKeyList(const String& keyList)
+{
+ return consumeKeyframeKeyList(CSSTokenizer(keyList).tokenRange());
+}
+
+bool CSSParserImpl::supportsDeclaration(CSSParserTokenRange& range)
+{
+ ASSERT(m_parsedProperties.isEmpty());
+ consumeDeclaration(range, StyleRule::Style);
+ bool result = !m_parsedProperties.isEmpty();
+ m_parsedProperties.clear();
+ return result;
+}
+
+void CSSParserImpl::parseDeclarationListForInspector(const String& declaration, const CSSParserContext& context, CSSParserObserver& observer)
+{
+ CSSParserObserverWrapper wrapper(observer);
+ CSSParserImpl parser(context, declaration, nullptr, &wrapper);
+ observer.startRuleHeader(StyleRule::Style, 0);
+ observer.endRuleHeader(1);
+ parser.consumeDeclarationList(parser.tokenizer()->tokenRange(), StyleRule::Style);
+}
+
+void CSSParserImpl::parseStyleSheetForInspector(const String& string, const CSSParserContext& context, StyleSheetContents* styleSheet, CSSParserObserver& observer)
+{
+ CSSParserObserverWrapper wrapper(observer);
+ CSSParserImpl parser(context, string, styleSheet, &wrapper);
+ bool firstRuleValid = parser.consumeRuleList(parser.tokenizer()->tokenRange(), TopLevelRuleList, [&styleSheet](RefPtr<StyleRuleBase> rule) {
+ if (rule->isCharsetRule())
+ return;
+ styleSheet->parserAppendRule(rule.releaseNonNull());
+ });
+ styleSheet->setHasSyntacticallyValidCSSHeader(firstRuleValid);
+}
+
+static CSSParserImpl::AllowedRulesType computeNewAllowedRules(CSSParserImpl::AllowedRulesType allowedRules, StyleRuleBase* rule)
+{
+ if (!rule || allowedRules == CSSParserImpl::KeyframeRules || allowedRules == CSSParserImpl::NoRules)
+ return allowedRules;
+ ASSERT(allowedRules <= CSSParserImpl::RegularRules);
+ if (rule->isCharsetRule() || rule->isImportRule())
+ return CSSParserImpl::AllowImportRules;
+ if (rule->isNamespaceRule())
+ return CSSParserImpl::AllowNamespaceRules;
+ return CSSParserImpl::RegularRules;
+}
+
+template<typename T>
+bool CSSParserImpl::consumeRuleList(CSSParserTokenRange range, RuleListType ruleListType, const T callback)
+{
+ AllowedRulesType allowedRules = RegularRules;
+ switch (ruleListType) {
+ case TopLevelRuleList:
+ allowedRules = AllowCharsetRules;
+ break;
+ case RegularRuleList:
+ allowedRules = RegularRules;
+ break;
+ case KeyframesRuleList:
+ allowedRules = KeyframeRules;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ bool seenRule = false;
+ bool firstRuleValid = false;
+ while (!range.atEnd()) {
+ RefPtr<StyleRuleBase> rule;
+ switch (range.peek().type()) {
+ case WhitespaceToken:
+ range.consumeWhitespace();
+ continue;
+ case AtKeywordToken:
+ rule = consumeAtRule(range, allowedRules);
+ break;
+ case CDOToken:
+ case CDCToken:
+ if (ruleListType == TopLevelRuleList) {
+ range.consume();
+ continue;
+ }
+ FALLTHROUGH;
+ default:
+ rule = consumeQualifiedRule(range, allowedRules);
+ break;
+ }
+ if (!seenRule) {
+ seenRule = true;
+ firstRuleValid = rule;
+ }
+ if (rule) {
+ allowedRules = computeNewAllowedRules(allowedRules, rule.get());
+ callback(rule);
+ }
+ }
+
+ return firstRuleValid;
+}
+
+RefPtr<StyleRuleBase> CSSParserImpl::consumeAtRule(CSSParserTokenRange& range, AllowedRulesType allowedRules)
+{
+ ASSERT(range.peek().type() == AtKeywordToken);
+ const StringView name = range.consumeIncludingWhitespace().value();
+ const CSSParserToken* preludeStart = &range.peek();
+ while (!range.atEnd() && range.peek().type() != LeftBraceToken && range.peek().type() != SemicolonToken)
+ range.consumeComponentValue();
+
+ CSSParserTokenRange prelude = range.makeSubRange(preludeStart, &range.peek());
+ CSSAtRuleID id = cssAtRuleID(name);
+
+ if (range.atEnd() || range.peek().type() == SemicolonToken) {
+ range.consume();
+ if (allowedRules == AllowCharsetRules && id == CSSAtRuleCharset)
+ return consumeCharsetRule(prelude);
+ if (allowedRules <= AllowImportRules && id == CSSAtRuleImport)
+ return consumeImportRule(prelude);
+ if (allowedRules <= AllowNamespaceRules && id == CSSAtRuleNamespace)
+ return consumeNamespaceRule(prelude);
+ // FIXME-NEWPARSER: Support "apply"
+ /*if (allowedRules == ApplyRules && id == CSSAtRuleApply) {
+ consumeApplyRule(prelude);
+ return nullptr; // consumeApplyRule just updates m_parsedProperties
+ }*/
+ return nullptr; // Parse error, unrecognised at-rule without block
+ }
+
+ CSSParserTokenRange block = range.consumeBlock();
+ if (allowedRules == KeyframeRules)
+ return nullptr; // Parse error, no at-rules supported inside @keyframes
+ if (allowedRules == NoRules || allowedRules == ApplyRules)
+ return nullptr; // Parse error, no at-rules with blocks supported inside declaration lists
+
+ ASSERT(allowedRules <= RegularRules);
+
+ switch (id) {
+ case CSSAtRuleMedia:
+ return consumeMediaRule(prelude, block);
+ case CSSAtRuleSupports:
+ return consumeSupportsRule(prelude, block);
+#if ENABLE(CSS_DEVICE_ADAPTATION)
+ case CSSAtRuleViewport:
+ return consumeViewportRule(prelude, block);
+#endif
+ case CSSAtRuleFontFace:
+ return consumeFontFaceRule(prelude, block);
+ case CSSAtRuleWebkitKeyframes:
+ return consumeKeyframesRule(true, prelude, block);
+ case CSSAtRuleKeyframes:
+ return consumeKeyframesRule(false, prelude, block);
+ case CSSAtRulePage:
+ return consumePageRule(prelude, block);
+#if ENABLE(CSS_REGIONS)
+ case CSSAtRuleWebkitRegion:
+ return consumeRegionRule(prelude, block);
+#endif
+ default:
+ return nullptr; // Parse error, unrecognised at-rule with block
+ }
+}
+
+RefPtr<StyleRuleBase> CSSParserImpl::consumeQualifiedRule(CSSParserTokenRange& range, AllowedRulesType allowedRules)
+{
+ const CSSParserToken* preludeStart = &range.peek();
+ while (!range.atEnd() && range.peek().type() != LeftBraceToken)
+ range.consumeComponentValue();
+
+ if (range.atEnd())
+ return nullptr; // Parse error, EOF instead of qualified rule block
+
+ CSSParserTokenRange prelude = range.makeSubRange(preludeStart, &range.peek());
+ CSSParserTokenRange block = range.consumeBlockCheckingForEditability(m_styleSheet.get());
+
+ if (allowedRules <= RegularRules)
+ return consumeStyleRule(prelude, block);
+ if (allowedRules == KeyframeRules)
+ return consumeKeyframeStyleRule(prelude, block);
+
+ ASSERT_NOT_REACHED();
+ return nullptr;
+}
+
+// This may still consume tokens if it fails
+static AtomicString consumeStringOrURI(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.peek();
+
+ if (token.type() == StringToken || token.type() == UrlToken)
+ return range.consumeIncludingWhitespace().value().toAtomicString();
+
+ if (token.type() != FunctionToken || !equalIgnoringASCIICase(token.value(), "url"))
+ return AtomicString();
+
+ CSSParserTokenRange contents = range.consumeBlock();
+ const CSSParserToken& uri = contents.consumeIncludingWhitespace();
+ if (uri.type() == BadStringToken || !contents.atEnd())
+ return AtomicString();
+ return uri.value().toAtomicString();
+}
+
+RefPtr<StyleRuleCharset> CSSParserImpl::consumeCharsetRule(CSSParserTokenRange prelude)
+{
+ const CSSParserToken& string = prelude.consumeIncludingWhitespace();
+ if (string.type() != StringToken || !prelude.atEnd())
+ return nullptr; // Parse error, expected a single string
+ return StyleRuleCharset::create();
+}
+
+RefPtr<StyleRuleImport> CSSParserImpl::consumeImportRule(CSSParserTokenRange prelude)
+{
+ AtomicString uri(consumeStringOrURI(prelude));
+ if (uri.isNull())
+ return nullptr; // Parse error, expected string or URI
+
+ if (m_observerWrapper) {
+ unsigned endOffset = m_observerWrapper->endOffset(prelude);
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Import, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(endOffset);
+ m_observerWrapper->observer().startRuleBody(endOffset);
+ m_observerWrapper->observer().endRuleBody(endOffset);
+ }
+
+ return StyleRuleImport::create(uri, MediaQueryParser::parseMediaQuerySet(prelude).releaseNonNull());
+}
+
+RefPtr<StyleRuleNamespace> CSSParserImpl::consumeNamespaceRule(CSSParserTokenRange prelude)
+{
+ AtomicString namespacePrefix;
+ if (prelude.peek().type() == IdentToken)
+ namespacePrefix = prelude.consumeIncludingWhitespace().value().toAtomicString();
+
+ AtomicString uri(consumeStringOrURI(prelude));
+ if (uri.isNull() || !prelude.atEnd())
+ return nullptr; // Parse error, expected string or URI
+
+ return StyleRuleNamespace::create(namespacePrefix, uri);
+}
+
+RefPtr<StyleRuleMedia> CSSParserImpl::consumeMediaRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ if (m_deferredParser)
+ return StyleRuleMedia::create(MediaQueryParser::parseMediaQuerySet(prelude).releaseNonNull(), std::make_unique<DeferredStyleGroupRuleList>(block, *m_deferredParser));
+
+ Vector<RefPtr<StyleRuleBase>> rules;
+
+ if (m_observerWrapper) {
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Media, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(m_observerWrapper->endOffset(prelude));
+ m_observerWrapper->observer().startRuleBody(m_observerWrapper->previousTokenStartOffset(block));
+ }
+
+ consumeRuleList(block, RegularRuleList, [&rules](RefPtr<StyleRuleBase> rule) {
+ rules.append(rule);
+ });
+
+ if (m_observerWrapper)
+ m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(block));
+
+ return StyleRuleMedia::create(MediaQueryParser::parseMediaQuerySet(prelude).releaseNonNull(), rules);
+}
+
+RefPtr<StyleRuleSupports> CSSParserImpl::consumeSupportsRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ CSSSupportsParser::SupportsResult supported = CSSSupportsParser::supportsCondition(prelude, *this);
+ if (supported == CSSSupportsParser::Invalid)
+ return nullptr; // Parse error, invalid @supports condition
+
+ if (m_deferredParser)
+ return StyleRuleSupports::create(prelude.serialize().stripWhiteSpace(), supported, std::make_unique<DeferredStyleGroupRuleList>(block, *m_deferredParser));
+
+ if (m_observerWrapper) {
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Supports, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(m_observerWrapper->endOffset(prelude));
+ m_observerWrapper->observer().startRuleBody(m_observerWrapper->previousTokenStartOffset(block));
+ }
+
+ Vector<RefPtr<StyleRuleBase>> rules;
+ consumeRuleList(block, RegularRuleList, [&rules](RefPtr<StyleRuleBase> rule) {
+ rules.append(rule);
+ });
+
+ if (m_observerWrapper)
+ m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(block));
+
+ return StyleRuleSupports::create(prelude.serialize().stripWhiteSpace(), supported, rules);
+}
+
+#if ENABLE(CSS_DEVICE_ADAPTATION)
+RefPtr<StyleRuleViewport> CSSParserImpl::consumeViewportRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ if (!prelude.atEnd())
+ return nullptr; // Parser error; @viewport prelude should be empty
+
+ if (m_observerWrapper) {
+ unsigned endOffset = m_observerWrapper->endOffset(prelude);
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Viewport, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(endOffset);
+ m_observerWrapper->observer().startRuleBody(endOffset);
+ m_observerWrapper->observer().endRuleBody(endOffset);
+ }
+
+ consumeDeclarationList(block, StyleRule::Viewport);
+ return StyleRuleViewport::create(createStyleProperties(m_parsedProperties, CSSViewportRuleMode));
+}
+#endif
+
+RefPtr<StyleRuleFontFace> CSSParserImpl::consumeFontFaceRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ if (!prelude.atEnd())
+ return nullptr; // Parse error; @font-face prelude should be empty
+
+ if (m_observerWrapper) {
+ unsigned endOffset = m_observerWrapper->endOffset(prelude);
+ m_observerWrapper->observer().startRuleHeader(StyleRule::FontFace, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(endOffset);
+ m_observerWrapper->observer().startRuleBody(endOffset);
+ m_observerWrapper->observer().endRuleBody(endOffset);
+ }
+
+ consumeDeclarationList(block, StyleRule::FontFace);
+ return StyleRuleFontFace::create(createStyleProperties(m_parsedProperties, m_context.mode));
+}
+
+RefPtr<StyleRuleKeyframes> CSSParserImpl::consumeKeyframesRule(bool webkitPrefixed, CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ CSSParserTokenRange rangeCopy = prelude; // For inspector callbacks
+ const CSSParserToken& nameToken = prelude.consumeIncludingWhitespace();
+ if (!prelude.atEnd())
+ return nullptr; // Parse error; expected single non-whitespace token in @keyframes header
+
+ String name;
+ if (nameToken.type() == IdentToken) {
+ name = nameToken.value().toString();
+ } else if (nameToken.type() == StringToken && webkitPrefixed)
+ name = nameToken.value().toString();
+ else
+ return nullptr; // Parse error; expected ident token in @keyframes header
+
+ if (m_deferredParser)
+ return StyleRuleKeyframes::create(name, std::make_unique<DeferredStyleGroupRuleList>(block, *m_deferredParser));
+
+ if (m_observerWrapper) {
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Keyframes, m_observerWrapper->startOffset(rangeCopy));
+ m_observerWrapper->observer().endRuleHeader(m_observerWrapper->endOffset(prelude));
+ m_observerWrapper->observer().startRuleBody(m_observerWrapper->previousTokenStartOffset(block));
+ m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(block));
+ }
+
+ RefPtr<StyleRuleKeyframes> keyframeRule = StyleRuleKeyframes::create(name);
+ consumeRuleList(block, KeyframesRuleList, [keyframeRule](const RefPtr<StyleRuleBase>& keyframe) {
+ keyframeRule->parserAppendKeyframe(downcast<const StyleRuleKeyframe>(keyframe.get()));
+ });
+
+ // FIXME-NEWPARSER: Find out why this is done. Behavior difference when prefixed?
+ // keyframeRule->setVendorPrefixed(webkitPrefixed);
+ return keyframeRule;
+}
+
+RefPtr<StyleRulePage> CSSParserImpl::consumePageRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ CSSSelectorList selectorList = parsePageSelector(prelude, m_styleSheet.get());
+ if (!selectorList.isValid())
+ return nullptr; // Parse error, invalid @page selector
+
+ if (m_observerWrapper) {
+ unsigned endOffset = m_observerWrapper->endOffset(prelude);
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Page, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(endOffset);
+ }
+
+ consumeDeclarationList(block, StyleRule::Style);
+
+ RefPtr<StyleRulePage> page = StyleRulePage::create(createStyleProperties(m_parsedProperties, m_context.mode));
+ page->wrapperAdoptSelectorList(selectorList);
+ return page;
+}
+
+#if ENABLE(CSS_REGIONS)
+RefPtr<StyleRuleRegion> CSSParserImpl::consumeRegionRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ CSSSelectorList selectorList = CSSSelectorParser::parseSelector(prelude, m_context, m_styleSheet.get());
+ if (!selectorList.isValid())
+ return nullptr; // Parse error, invalid selector list
+
+ if (m_observerWrapper) {
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Region, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(m_observerWrapper->endOffset(prelude));
+ m_observerWrapper->observer().startRuleBody(m_observerWrapper->previousTokenStartOffset(block));
+ }
+
+ Vector<RefPtr<StyleRuleBase>> rules;
+ consumeRuleList(block, RegularRuleList, [&rules](RefPtr<StyleRuleBase> rule) {
+ rules.append(rule);
+ });
+
+ if (m_observerWrapper)
+ m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(block));
+
+ return StyleRuleRegion::create(selectorList, rules);
+
+}
+#endif
+
+// FIXME-NEWPARSER: Support "apply"
+/*void CSSParserImpl::consumeApplyRule(CSSParserTokenRange prelude)
+{
+ const CSSParserToken& ident = prelude.consumeIncludingWhitespace();
+ if (!prelude.atEnd() || !CSSVariableParser::isValidVariableName(ident))
+ return; // Parse error, expected a single custom property name
+ m_parsedProperties.append(CSSProperty(
+ CSSPropertyApplyAtRule,
+ *CSSCustomIdentValue::create(ident.value().toString())));
+}
+*/
+
+RefPtr<StyleRuleKeyframe> CSSParserImpl::consumeKeyframeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ std::unique_ptr<Vector<double>> keyList = consumeKeyframeKeyList(prelude);
+ if (!keyList)
+ return nullptr;
+
+ if (m_observerWrapper) {
+ m_observerWrapper->observer().startRuleHeader(StyleRule::Keyframe, m_observerWrapper->startOffset(prelude));
+ m_observerWrapper->observer().endRuleHeader(m_observerWrapper->endOffset(prelude));
+ }
+
+ consumeDeclarationList(block, StyleRule::Keyframe);
+ return StyleRuleKeyframe::create(WTFMove(keyList), createStyleProperties(m_parsedProperties, m_context.mode));
+}
+
+static void observeSelectors(CSSParserObserverWrapper& wrapper, CSSParserTokenRange selectors)
+{
+ // This is easier than hooking into the CSSSelectorParser
+ selectors.consumeWhitespace();
+ CSSParserTokenRange originalRange = selectors;
+ wrapper.observer().startRuleHeader(StyleRule::Style, wrapper.startOffset(originalRange));
+
+ while (!selectors.atEnd()) {
+ const CSSParserToken* selectorStart = &selectors.peek();
+ while (!selectors.atEnd() && selectors.peek().type() != CommaToken)
+ selectors.consumeComponentValue();
+ CSSParserTokenRange selector = selectors.makeSubRange(selectorStart, &selectors.peek());
+ selectors.consumeIncludingWhitespace();
+
+ wrapper.observer().observeSelector(wrapper.startOffset(selector), wrapper.endOffset(selector));
+ }
+
+ wrapper.observer().endRuleHeader(wrapper.endOffset(originalRange));
+}
+
+RefPtr<StyleRule> CSSParserImpl::consumeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
+{
+ CSSSelectorList selectorList = CSSSelectorParser::parseSelector(prelude, m_context, m_styleSheet.get());
+ if (!selectorList.isValid())
+ return nullptr; // Parse error, invalid selector list
+
+ RefPtr<StyleRule> rule;
+ if (m_observerWrapper)
+ observeSelectors(*m_observerWrapper, prelude);
+
+ if (m_deferredParser) {
+ // If a rule is empty (i.e., only whitespace), don't bother using
+ // deferred parsing. This allows the empty rule optimization in ElementRuleCollector
+ // to continue to work. Note we don't have to consider CommentTokens, since those
+ // are stripped out.
+ CSSParserTokenRange blockCopy = block;
+ blockCopy.consumeWhitespace();
+ if (!blockCopy.atEnd()) {
+ rule = StyleRule::create(createDeferredStyleProperties(block));
+ rule->wrapperAdoptSelectorList(selectorList);
+ return rule;
+ }
+ }
+
+ consumeDeclarationList(block, StyleRule::Style);
+ rule = StyleRule::create(createStyleProperties(m_parsedProperties, m_context.mode));
+ rule->wrapperAdoptSelectorList(selectorList);
+ return rule;
+}
+
+void CSSParserImpl::consumeDeclarationList(CSSParserTokenRange range, StyleRule::Type ruleType)
+{
+ ASSERT(m_parsedProperties.isEmpty());
+
+ bool useObserver = m_observerWrapper && (ruleType == StyleRule::Style || ruleType == StyleRule::Keyframe);
+ if (useObserver) {
+ m_observerWrapper->observer().startRuleBody(m_observerWrapper->previousTokenStartOffset(range));
+ m_observerWrapper->skipCommentsBefore(range, true);
+ }
+
+ while (!range.atEnd()) {
+ switch (range.peek().type()) {
+ case WhitespaceToken:
+ case SemicolonToken:
+ range.consume();
+ break;
+ case IdentToken: {
+ const CSSParserToken* declarationStart = &range.peek();
+
+ if (useObserver)
+ m_observerWrapper->yieldCommentsBefore(range);
+
+ while (!range.atEnd() && range.peek().type() != SemicolonToken)
+ range.consumeComponentValue();
+
+ consumeDeclaration(range.makeSubRange(declarationStart, &range.peek()), ruleType);
+
+ if (useObserver)
+ m_observerWrapper->skipCommentsBefore(range, false);
+ break;
+ }
+ case AtKeywordToken: {
+ // FIXME-NEWPARSER: Support apply
+ AllowedRulesType allowedRules = /* ruleType == StyleRule::Style && RuntimeEnabledFeatures::cssApplyAtRulesEnabled() ? ApplyRules :*/ NoRules;
+ RefPtr<StyleRuleBase> rule = consumeAtRule(range, allowedRules);
+ ASSERT_UNUSED(rule, !rule);
+ break;
+ }
+ default: // Parse error, unexpected token in declaration list
+ while (!range.atEnd() && range.peek().type() != SemicolonToken)
+ range.consumeComponentValue();
+ break;
+ }
+ }
+
+ // Yield remaining comments
+ if (useObserver) {
+ m_observerWrapper->yieldCommentsBefore(range);
+ m_observerWrapper->observer().endRuleBody(m_observerWrapper->endOffset(range));
+ }
+}
+
+void CSSParserImpl::consumeDeclaration(CSSParserTokenRange range, StyleRule::Type ruleType)
+{
+ CSSParserTokenRange rangeCopy = range; // For inspector callbacks
+
+ ASSERT(range.peek().type() == IdentToken);
+ const CSSParserToken& token = range.consumeIncludingWhitespace();
+ CSSPropertyID propertyID = token.parseAsCSSPropertyID();
+ if (range.consume().type() != ColonToken)
+ return; // Parse error
+
+ bool important = false;
+ const CSSParserToken* declarationValueEnd = range.end();
+ const CSSParserToken* last = range.end() - 1;
+ while (last->type() == WhitespaceToken)
+ --last;
+ if (last->type() == IdentToken && equalIgnoringASCIICase(last->value(), "important")) {
+ --last;
+ while (last->type() == WhitespaceToken)
+ --last;
+ if (last->type() == DelimiterToken && last->delimiter() == '!') {
+ important = true;
+ declarationValueEnd = last;
+ }
+ }
+
+ size_t propertiesCount = m_parsedProperties.size();
+ if (propertyID == CSSPropertyInvalid && CSSVariableParser::isValidVariableName(token)) {
+ AtomicString variableName = token.value().toAtomicString();
+ consumeCustomPropertyValue(range.makeSubRange(&range.peek(), declarationValueEnd), variableName, important);
+ }
+
+ if (important && (ruleType == StyleRule::FontFace || ruleType == StyleRule::Keyframe))
+ return;
+
+ if (propertyID != CSSPropertyInvalid)
+ consumeDeclarationValue(range.makeSubRange(&range.peek(), declarationValueEnd), propertyID, important, ruleType);
+
+ if (m_observerWrapper && (ruleType == StyleRule::Style || ruleType == StyleRule::Keyframe)) {
+ m_observerWrapper->observer().observeProperty(
+ m_observerWrapper->startOffset(rangeCopy), m_observerWrapper->endOffset(rangeCopy),
+ important, m_parsedProperties.size() != propertiesCount);
+ }
+}
+
+void CSSParserImpl::consumeCustomPropertyValue(CSSParserTokenRange range, const AtomicString& variableName, bool important)
+{
+ if (RefPtr<CSSCustomPropertyValue> value = CSSVariableParser::parseDeclarationValue(variableName, range))
+ m_parsedProperties.append(CSSProperty(CSSPropertyCustom, WTFMove(value), important));
+}
+
+void CSSParserImpl::consumeDeclarationValue(CSSParserTokenRange range, CSSPropertyID propertyID, bool important, StyleRule::Type ruleType)
+{
+ CSSPropertyParser::parseValue(propertyID, important, range, m_context, m_styleSheet.get(), m_parsedProperties, ruleType);
+}
+
+std::unique_ptr<Vector<double>> CSSParserImpl::consumeKeyframeKeyList(CSSParserTokenRange range)
+{
+ std::unique_ptr<Vector<double>> result = std::unique_ptr<Vector<double>>(new Vector<double>);
+ while (true) {
+ range.consumeWhitespace();
+ const CSSParserToken& token = range.consumeIncludingWhitespace();
+ if (token.type() == PercentageToken && token.numericValue() >= 0 && token.numericValue() <= 100)
+ result->append(token.numericValue() / 100);
+ else if (token.type() == IdentToken && equalIgnoringASCIICase(token.value(), "from"))
+ result->append(0);
+ else if (token.type() == IdentToken && equalIgnoringASCIICase(token.value(), "to"))
+ result->append(1);
+ else
+ return nullptr; // Parser error, invalid value in keyframe selector
+ if (range.atEnd())
+ return result;
+ if (range.consume().type() != CommaToken)
+ return nullptr; // Parser error
+ }
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserImpl.h b/Source/WebCore/css/parser/CSSParserImpl.h
new file mode 100644
index 000000000..302c22b1e
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserImpl.h
@@ -0,0 +1,176 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSDeferredParser.h"
+#include "CSSParser.h"
+#include "CSSParserMode.h"
+#include "CSSParserTokenRange.h"
+#include "CSSProperty.h"
+#include "CSSPropertyNames.h"
+#include "CSSPropertySourceData.h"
+#include "StyleRule.h"
+
+#include <memory>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CSSParserObserver;
+class CSSParserObserverWrapper;
+class CSSSelectorList;
+class CSSTokenizer;
+class StyleRuleKeyframe;
+class StyleRule;
+class StyleRuleBase;
+class StyleRuleCharset;
+class StyleRuleFontFace;
+class StyleRuleImport;
+class StyleRuleKeyframes;
+class StyleRuleMedia;
+class StyleRuleNamespace;
+class StyleRulePage;
+class StyleRuleSupports;
+class StyleRuleViewport;
+class StyleSheetContents;
+class ImmutableStyleProperties;
+class Element;
+class MutableStyleProperties;
+
+class CSSParserImpl {
+ WTF_MAKE_NONCOPYABLE(CSSParserImpl);
+public:
+ CSSParserImpl(const CSSParserContext&, const String&, StyleSheetContents* = nullptr, CSSParserObserverWrapper* = nullptr, CSSParser::RuleParsing = CSSParser::RuleParsing::Normal);
+
+ enum AllowedRulesType {
+ // As per css-syntax, css-cascade and css-namespaces, @charset rules
+ // must come first, followed by @import then @namespace.
+ // AllowImportRules actually means we allow @import and any rules thay
+ // may follow it, i.e. @namespace rules and regular rules.
+ // AllowCharsetRules and AllowNamespaceRules behave similarly.
+ AllowCharsetRules,
+ AllowImportRules,
+ AllowNamespaceRules,
+ RegularRules,
+ KeyframeRules,
+ ApplyRules, // For @apply inside style rules
+ NoRules, // For parsing at-rules inside declaration lists
+ };
+
+ static CSSParser::ParseResult parseValue(MutableStyleProperties*, CSSPropertyID, const String&, bool important, const CSSParserContext&);
+ static CSSParser::ParseResult parseCustomPropertyValue(MutableStyleProperties*, const AtomicString& propertyName, const String&, bool important, const CSSParserContext&);
+ static Ref<ImmutableStyleProperties> parseInlineStyleDeclaration(const String&, Element*);
+ static bool parseDeclarationList(MutableStyleProperties*, const String&, const CSSParserContext&);
+ static RefPtr<StyleRuleBase> parseRule(const String&, const CSSParserContext&, StyleSheetContents*, AllowedRulesType);
+ static void parseStyleSheet(const String&, const CSSParserContext&, StyleSheetContents*, CSSParser::RuleParsing);
+ static CSSSelectorList parsePageSelector(CSSParserTokenRange, StyleSheetContents*);
+
+ static std::unique_ptr<Vector<double>> parseKeyframeKeyList(const String&);
+
+ bool supportsDeclaration(CSSParserTokenRange&);
+
+ static void parseDeclarationListForInspector(const String&, const CSSParserContext&, CSSParserObserver&);
+ static void parseStyleSheetForInspector(const String&, const CSSParserContext&, StyleSheetContents*, CSSParserObserver&);
+
+ static Ref<ImmutableStyleProperties> parseDeferredDeclaration(CSSParserTokenRange, const CSSParserContext&, StyleSheetContents*);
+ static void parseDeferredRuleList(CSSParserTokenRange, CSSDeferredParser&, Vector<RefPtr<StyleRuleBase>>&);
+ static void parseDeferredKeyframeList(CSSParserTokenRange, CSSDeferredParser&, StyleRuleKeyframes&);
+
+ CSSTokenizer* tokenizer() const { return m_tokenizer.get(); };
+ CSSDeferredParser* deferredParser() const { return m_deferredParser.get(); }
+
+private:
+ CSSParserImpl(const CSSParserContext&, StyleSheetContents*);
+ CSSParserImpl(CSSDeferredParser&);
+
+ enum RuleListType {
+ TopLevelRuleList,
+ RegularRuleList,
+ KeyframesRuleList
+ };
+
+ // Returns whether the first encountered rule was valid
+ template<typename T>
+ bool consumeRuleList(CSSParserTokenRange, RuleListType, T callback);
+
+ // These two functions update the range they're given
+ RefPtr<StyleRuleBase> consumeAtRule(CSSParserTokenRange&, AllowedRulesType);
+ RefPtr<StyleRuleBase> consumeQualifiedRule(CSSParserTokenRange&, AllowedRulesType);
+
+ static RefPtr<StyleRuleCharset> consumeCharsetRule(CSSParserTokenRange prelude);
+ RefPtr<StyleRuleImport> consumeImportRule(CSSParserTokenRange prelude);
+ RefPtr<StyleRuleNamespace> consumeNamespaceRule(CSSParserTokenRange prelude);
+ RefPtr<StyleRuleMedia> consumeMediaRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+ RefPtr<StyleRuleSupports> consumeSupportsRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+ RefPtr<StyleRuleViewport> consumeViewportRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+ RefPtr<StyleRuleFontFace> consumeFontFaceRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+ RefPtr<StyleRuleKeyframes> consumeKeyframesRule(bool webkitPrefixed, CSSParserTokenRange prelude, CSSParserTokenRange block);
+ RefPtr<StyleRulePage> consumePageRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+#if ENABLE(CSS_REGIONS)
+ RefPtr<StyleRuleRegion> consumeRegionRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+#endif
+ // Updates m_parsedProperties
+
+ // FIXME-NEWPARSER: Support "apply"
+ // void consumeApplyRule(CSSParserTokenRange prelude);
+
+ RefPtr<StyleRuleKeyframe> consumeKeyframeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+ RefPtr<StyleRule> consumeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+
+ void consumeDeclarationList(CSSParserTokenRange, StyleRule::Type);
+ void consumeDeclaration(CSSParserTokenRange, StyleRule::Type);
+ void consumeDeclarationValue(CSSParserTokenRange, CSSPropertyID, bool important, StyleRule::Type);
+ void consumeCustomPropertyValue(CSSParserTokenRange, const AtomicString& propertyName, bool important);
+
+ static std::unique_ptr<Vector<double>> consumeKeyframeKeyList(CSSParserTokenRange);
+
+ Ref<DeferredStyleProperties> createDeferredStyleProperties(const CSSParserTokenRange& propertyRange);
+
+ void adoptTokenizerEscapedStrings();
+
+ // FIXME: Can we build StylePropertySets directly?
+ // FIXME: Investigate using a smaller inline buffer
+ ParsedPropertyVector m_parsedProperties;
+ const CSSParserContext& m_context;
+
+ RefPtr<StyleSheetContents> m_styleSheet;
+
+ // For deferred property parsing.
+ RefPtr<CSSDeferredParser> m_deferredParser;
+
+ // For normal parsing.
+ std::unique_ptr<CSSTokenizer> m_tokenizer;
+
+ // For the inspector
+ CSSParserObserverWrapper* m_observerWrapper { nullptr };
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserMode.h b/Source/WebCore/css/parser/CSSParserMode.h
new file mode 100644
index 000000000..2f926a44d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserMode.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved.
+ * Copyright (C) 2012 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "TextEncoding.h"
+#include "URL.h"
+#include "URLHash.h"
+#include <wtf/HashFunctions.h>
+#include <wtf/text/StringHash.h>
+
+namespace WebCore {
+
+class Document;
+
+// Must not grow beyond 3 bits, due to packing in StyleProperties.
+enum CSSParserMode {
+ HTMLStandardMode,
+ HTMLQuirksMode,
+ // HTML attributes are parsed in quirks mode but also allows internal properties and values.
+ HTMLAttributeMode,
+ // SVG attributes are parsed in quirks mode but rules differ slightly.
+ SVGAttributeMode,
+ // @viewport rules are parsed in standards mode but CSSOM modifications (via StylePropertySet)
+ // must call parseViewportProperties so needs a special mode.
+ CSSViewportRuleMode,
+ // User agent stylesheets are parsed in standards mode but also allows internal properties and values.
+ UASheetMode
+};
+
+inline bool isQuirksModeBehavior(CSSParserMode mode)
+{
+ return mode == HTMLQuirksMode || mode == HTMLAttributeMode;
+}
+
+inline bool isUASheetBehavior(CSSParserMode mode)
+{
+ return mode == UASheetMode;
+}
+
+inline bool isUnitLessValueParsingEnabledForMode(CSSParserMode mode)
+{
+ return mode == HTMLAttributeMode || mode == SVGAttributeMode;
+}
+
+inline bool isCSSViewportParsingEnabledForMode(CSSParserMode mode)
+{
+ return mode == CSSViewportRuleMode;
+}
+
+// FIXME-NEWPARSER: Next two functions should be removed eventually.
+inline CSSParserMode strictToCSSParserMode(bool inStrictMode)
+{
+ return inStrictMode ? HTMLStandardMode : HTMLQuirksMode;
+}
+
+inline bool isStrictParserMode(CSSParserMode cssParserMode)
+{
+ return cssParserMode == UASheetMode || cssParserMode == HTMLStandardMode || cssParserMode == SVGAttributeMode;
+}
+
+struct CSSParserContext {
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ CSSParserContext(CSSParserMode, const URL& baseURL = URL());
+ WEBCORE_EXPORT CSSParserContext(Document&, const URL& baseURL = URL(), const String& charset = emptyString());
+
+ URL baseURL;
+ String charset;
+ CSSParserMode mode { HTMLStandardMode };
+ bool isHTMLDocument { false };
+ bool cssGridLayoutEnabled { false };
+#if ENABLE(TEXT_AUTOSIZING)
+ bool textAutosizingEnabled { false };
+#endif
+ bool needsSiteSpecificQuirks { false };
+ bool enforcesCSSMIMETypeInNoQuirksMode { true };
+ bool useLegacyBackgroundSizeShorthandBehavior { false };
+ bool springTimingFunctionEnabled { false };
+
+ bool deferredCSSParserEnabled { false };
+
+ URL completeURL(const String& url) const
+ {
+ if (url.isNull())
+ return URL();
+ if (charset.isEmpty())
+ return URL(baseURL, url);
+ return URL(baseURL, url, TextEncoding(charset));
+ }
+};
+
+bool operator==(const CSSParserContext&, const CSSParserContext&);
+inline bool operator!=(const CSSParserContext& a, const CSSParserContext& b) { return !(a == b); }
+
+WEBCORE_EXPORT const CSSParserContext& strictCSSParserContext();
+
+struct CSSParserContextHash {
+ static unsigned hash(const CSSParserContext& key)
+ {
+ auto hash = URLHash::hash(key.baseURL);
+ if (!key.charset.isEmpty())
+ hash ^= StringHash::hash(key.charset);
+ unsigned bits = key.isHTMLDocument << 0
+ & key.isHTMLDocument << 1
+ & key.cssGridLayoutEnabled << 2
+#if ENABLE(TEXT_AUTOSIZING)
+ & key.textAutosizingEnabled << 3
+#endif
+ & key.needsSiteSpecificQuirks << 4
+ & key.enforcesCSSMIMETypeInNoQuirksMode << 5
+ & key.useLegacyBackgroundSizeShorthandBehavior << 6
+ & key.springTimingFunctionEnabled << 7
+ & key.deferredCSSParserEnabled << 8
+ & key.mode << 9;
+ hash ^= WTF::intHash(bits);
+ return hash;
+ }
+ static bool equal(const CSSParserContext& a, const CSSParserContext& b)
+ {
+ return a == b;
+ }
+ static const bool safeToCompareToEmptyOrDeleted = false;
+};
+
+} // namespace WebCore
+
+namespace WTF {
+template<> struct HashTraits<WebCore::CSSParserContext> : GenericHashTraits<WebCore::CSSParserContext> {
+ static void constructDeletedValue(WebCore::CSSParserContext& slot) { new (NotNull, &slot.baseURL) WebCore::URL(WTF::HashTableDeletedValue); }
+ static bool isDeletedValue(const WebCore::CSSParserContext& value) { return value.baseURL.isHashTableDeletedValue(); }
+ static WebCore::CSSParserContext emptyValue() { return WebCore::CSSParserContext(WebCore::HTMLStandardMode); }
+};
+
+template<> struct DefaultHash<WebCore::CSSParserContext> {
+ typedef WebCore::CSSParserContextHash Hash;
+};
+} // namespace WTF
diff --git a/Source/WebCore/css/parser/CSSParserObserver.h b/Source/WebCore/css/parser/CSSParserObserver.h
new file mode 100644
index 000000000..5c4539caa
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserObserver.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003 Lars Knoll (knoll@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2008, 2009, 2010, 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
+ * Copyright (C) 2009 - 2010 Torch Mobile (Beijing) Co. Ltd. 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.
+ */
+
+#pragma once
+
+#include "CSSPropertySourceData.h"
+#include "StyleRule.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class CSSParserToken;
+class CSSParserTokenRange;
+
+// This is only for the inspector and shouldn't be used elsewhere.
+class CSSParserObserver {
+public:
+ virtual ~CSSParserObserver() { };
+ virtual void startRuleHeader(StyleRule::Type, unsigned offset) = 0;
+ virtual void endRuleHeader(unsigned offset) = 0;
+ virtual void observeSelector(unsigned startOffset, unsigned endOffset) = 0;
+ virtual void startRuleBody(unsigned offset) = 0;
+ virtual void endRuleBody(unsigned offset) = 0;
+ virtual void observeProperty(unsigned startOffset, unsigned endOffset, bool isImportant, bool isParsed) = 0;
+ virtual void observeComment(unsigned startOffset, unsigned endOffset) = 0;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserObserverWrapper.cpp b/Source/WebCore/css/parser/CSSParserObserverWrapper.cpp
new file mode 100644
index 000000000..65b224853
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserObserverWrapper.cpp
@@ -0,0 +1,72 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSParserObserverWrapper.h"
+
+#include "CSSParserTokenRange.h"
+
+namespace WebCore {
+
+unsigned CSSParserObserverWrapper::startOffset(const CSSParserTokenRange& range)
+{
+ return m_tokenOffsets[range.begin() - m_firstParserToken];
+}
+
+unsigned CSSParserObserverWrapper::previousTokenStartOffset(const CSSParserTokenRange& range)
+{
+ if (range.begin() == m_firstParserToken)
+ return 0;
+ return m_tokenOffsets[range.begin() - m_firstParserToken - 1];
+}
+
+unsigned CSSParserObserverWrapper::endOffset(const CSSParserTokenRange& range)
+{
+ return m_tokenOffsets[range.end() - m_firstParserToken];
+}
+
+void CSSParserObserverWrapper::skipCommentsBefore(const CSSParserTokenRange& range, bool leaveDirectlyBefore)
+{
+ unsigned startIndex = range.begin() - m_firstParserToken;
+ if (!leaveDirectlyBefore)
+ startIndex++;
+ while (m_commentIterator < m_commentOffsets.end() && m_commentIterator->tokensBefore < startIndex)
+ m_commentIterator++;
+}
+
+void CSSParserObserverWrapper::yieldCommentsBefore(const CSSParserTokenRange& range)
+{
+ unsigned startIndex = range.begin() - m_firstParserToken;
+ while (m_commentIterator < m_commentOffsets.end() && m_commentIterator->tokensBefore <= startIndex) {
+ m_observer.observeComment(m_commentIterator->startOffset, m_commentIterator->endOffset);
+ m_commentIterator++;
+ }
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserObserverWrapper.h b/Source/WebCore/css/parser/CSSParserObserverWrapper.h
new file mode 100644
index 000000000..456c0cd43
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserObserverWrapper.h
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserObserver.h"
+
+namespace WebCore {
+
+class CSSParserObserverWrapper {
+public:
+ explicit CSSParserObserverWrapper(CSSParserObserver& observer)
+ : m_observer(observer)
+ { }
+
+ unsigned startOffset(const CSSParserTokenRange&);
+ unsigned previousTokenStartOffset(const CSSParserTokenRange&);
+ unsigned endOffset(const CSSParserTokenRange&); // Includes trailing comments
+
+ void skipCommentsBefore(const CSSParserTokenRange&, bool leaveDirectlyBefore);
+ void yieldCommentsBefore(const CSSParserTokenRange&);
+
+ CSSParserObserver& observer() { return m_observer; }
+ void addComment(unsigned startOffset, unsigned endOffset, unsigned tokensBefore)
+ {
+ CommentPosition position = {startOffset, endOffset, tokensBefore};
+ m_commentOffsets.append(position);
+ }
+ void addToken(unsigned startOffset) { m_tokenOffsets.append(startOffset); }
+ void finalizeConstruction(CSSParserToken* firstParserToken)
+ {
+ m_firstParserToken = firstParserToken;
+ m_commentIterator = m_commentOffsets.begin();
+ }
+
+private:
+ CSSParserObserver& m_observer;
+ Vector<unsigned> m_tokenOffsets;
+ CSSParserToken* m_firstParserToken;
+
+ struct CommentPosition {
+ unsigned startOffset;
+ unsigned endOffset;
+ unsigned tokensBefore;
+ };
+
+ Vector<CommentPosition> m_commentOffsets;
+ Vector<CommentPosition>::iterator m_commentIterator;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserSelector.cpp b/Source/WebCore/css/parser/CSSParserSelector.cpp
new file mode 100644
index 000000000..808026d92
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserSelector.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2003 Lars Knoll (knoll@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2008, 2014 Apple Inc. 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 "CSSParserSelector.h"
+
+#include "CSSCustomPropertyValue.h"
+#include "CSSParserIdioms.h"
+#include "CSSPrimitiveValue.h"
+#include "CSSFunctionValue.h"
+#include "CSSSelector.h"
+#include "CSSSelectorList.h"
+#include "SelectorPseudoTypeMap.h"
+
+#if COMPILER(MSVC)
+// See https://msdn.microsoft.com/en-us/library/1wea5zwe.aspx
+#pragma warning(disable: 4701)
+#endif
+
+namespace WebCore {
+
+using namespace WTF;
+
+CSSParserSelector* CSSParserSelector::parsePagePseudoSelector(const AtomicString& pseudoTypeString)
+{
+ CSSSelector::PagePseudoClassType pseudoType;
+ if (equalLettersIgnoringASCIICase(pseudoTypeString, "first"))
+ pseudoType = CSSSelector::PagePseudoClassFirst;
+ else if (equalLettersIgnoringASCIICase(pseudoTypeString, "left"))
+ pseudoType = CSSSelector::PagePseudoClassLeft;
+ else if (equalLettersIgnoringASCIICase(pseudoTypeString, "right"))
+ pseudoType = CSSSelector::PagePseudoClassRight;
+ else
+ return nullptr;
+
+ auto selector = std::make_unique<CSSParserSelector>();
+ selector->m_selector->setMatch(CSSSelector::PagePseudoClass);
+ selector->m_selector->setPagePseudoType(pseudoType);
+ return selector.release();
+}
+
+CSSParserSelector* CSSParserSelector::parsePseudoElementSelectorFromStringView(StringView& pseudoTypeString)
+{
+ AtomicString name = pseudoTypeString.toAtomicString();
+
+ CSSSelector::PseudoElementType pseudoType = CSSSelector::parsePseudoElementType(name);
+ if (pseudoType == CSSSelector::PseudoElementUnknown) {
+ // FIXME-NEWPARSER: We can't add "slotted" to the map without breaking the old
+ // parser, so this hack ensures the new parser still recognizes it. When the new
+ // parser turns on, we can add "slotted" to the map and remove this code.
+ if (pseudoTypeString.startsWithIgnoringASCIICase("slotted"))
+ pseudoType = CSSSelector::PseudoElementSlotted;
+ else
+ return nullptr;
+ }
+
+ auto selector = std::make_unique<CSSParserSelector>();
+ selector->m_selector->setMatch(CSSSelector::PseudoElement);
+ selector->m_selector->setPseudoElementType(pseudoType);
+ if (pseudoType == CSSSelector::PseudoElementWebKitCustomLegacyPrefixed) {
+ ASSERT_WITH_MESSAGE(name == "-webkit-input-placeholder", "-webkit-input-placeholder is the only LegacyPrefix pseudo type.");
+ if (name == "-webkit-input-placeholder")
+ name = AtomicString("placeholder", AtomicString::ConstructFromLiteral);
+ }
+ selector->m_selector->setValue(name);
+ return selector.release();
+}
+
+CSSParserSelector* CSSParserSelector::parsePseudoClassSelectorFromStringView(StringView& pseudoTypeString)
+{
+ PseudoClassOrCompatibilityPseudoElement pseudoType = parsePseudoClassAndCompatibilityElementString(pseudoTypeString);
+ if (pseudoType.pseudoClass != CSSSelector::PseudoClassUnknown) {
+ auto selector = std::make_unique<CSSParserSelector>();
+ selector->m_selector->setMatch(CSSSelector::PseudoClass);
+ selector->m_selector->setPseudoClassType(pseudoType.pseudoClass);
+ return selector.release();
+ }
+ if (pseudoType.compatibilityPseudoElement != CSSSelector::PseudoElementUnknown) {
+ auto selector = std::make_unique<CSSParserSelector>();
+ selector->m_selector->setMatch(CSSSelector::PseudoElement);
+ selector->m_selector->setPseudoElementType(pseudoType.compatibilityPseudoElement);
+ AtomicString name = pseudoTypeString.toAtomicString();
+ selector->m_selector->setValue(name);
+ return selector.release();
+ }
+ return nullptr;
+}
+
+CSSParserSelector::CSSParserSelector()
+ : m_selector(std::make_unique<CSSSelector>())
+{
+}
+
+CSSParserSelector::CSSParserSelector(const QualifiedName& tagQName)
+ : m_selector(std::make_unique<CSSSelector>(tagQName))
+{
+}
+
+CSSParserSelector::~CSSParserSelector()
+{
+ if (!m_tagHistory)
+ return;
+ Vector<std::unique_ptr<CSSParserSelector>, 16> toDelete;
+ std::unique_ptr<CSSParserSelector> selector = WTFMove(m_tagHistory);
+ while (true) {
+ std::unique_ptr<CSSParserSelector> next = WTFMove(selector->m_tagHistory);
+ toDelete.append(WTFMove(selector));
+ if (!next)
+ break;
+ selector = WTFMove(next);
+ }
+}
+
+void CSSParserSelector::adoptSelectorVector(Vector<std::unique_ptr<CSSParserSelector>>& selectorVector)
+{
+ auto selectorList = std::make_unique<CSSSelectorList>();
+ selectorList->adoptSelectorVector(selectorVector);
+ m_selector->setSelectorList(WTFMove(selectorList));
+}
+
+void CSSParserSelector::setLangArgumentList(std::unique_ptr<Vector<AtomicString>> argumentList)
+{
+ ASSERT_WITH_MESSAGE(!argumentList->isEmpty(), "No CSS Selector takes an empty argument list.");
+ m_selector->setLangArgumentList(WTFMove(argumentList));
+}
+
+void CSSParserSelector::setSelectorList(std::unique_ptr<CSSSelectorList> selectorList)
+{
+ m_selector->setSelectorList(WTFMove(selectorList));
+}
+
+static bool selectorListMatchesPseudoElement(const CSSSelectorList* selectorList)
+{
+ if (!selectorList)
+ return false;
+
+ for (const CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) {
+ for (const CSSSelector* selector = subSelector; selector; selector = selector->tagHistory()) {
+ if (selector->matchesPseudoElement())
+ return true;
+ if (const CSSSelectorList* subselectorList = selector->selectorList()) {
+ if (selectorListMatchesPseudoElement(subselectorList))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool CSSParserSelector::matchesPseudoElement() const
+{
+ return m_selector->matchesPseudoElement() || selectorListMatchesPseudoElement(m_selector->selectorList());
+}
+
+void CSSParserSelector::insertTagHistory(CSSSelector::RelationType before, std::unique_ptr<CSSParserSelector> selector, CSSSelector::RelationType after)
+{
+ if (m_tagHistory)
+ selector->setTagHistory(WTFMove(m_tagHistory));
+ setRelation(before);
+ selector->setRelation(after);
+ m_tagHistory = WTFMove(selector);
+}
+
+void CSSParserSelector::appendTagHistory(CSSSelector::RelationType relation, std::unique_ptr<CSSParserSelector> selector)
+{
+ CSSParserSelector* end = this;
+ while (end->tagHistory())
+ end = end->tagHistory();
+
+ end->setRelation(relation);
+ end->setTagHistory(WTFMove(selector));
+}
+
+void CSSParserSelector::appendTagHistory(CSSParserSelectorCombinator relation, std::unique_ptr<CSSParserSelector> selector)
+{
+ CSSParserSelector* end = this;
+ while (end->tagHistory())
+ end = end->tagHistory();
+
+ CSSSelector::RelationType selectorRelation;
+ switch (relation) {
+ case CSSParserSelectorCombinator::Child:
+ selectorRelation = CSSSelector::Child;
+ break;
+ case CSSParserSelectorCombinator::DescendantSpace:
+ selectorRelation = CSSSelector::DescendantSpace;
+ break;
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ case CSSParserSelectorCombinator::DescendantDoubleChild:
+ selectorRelation = CSSSelector::DescendantDoubleChild;
+ break;
+#endif
+ case CSSParserSelectorCombinator::DirectAdjacent:
+ selectorRelation = CSSSelector::DirectAdjacent;
+ break;
+ case CSSParserSelectorCombinator::IndirectAdjacent:
+ selectorRelation = CSSSelector::IndirectAdjacent;
+ break;
+ }
+ end->setRelation(selectorRelation);
+ end->setTagHistory(WTFMove(selector));
+}
+
+void CSSParserSelector::prependTagSelector(const QualifiedName& tagQName, bool tagIsForNamespaceRule)
+{
+ auto second = std::make_unique<CSSParserSelector>();
+ second->m_selector = WTFMove(m_selector);
+ second->m_tagHistory = WTFMove(m_tagHistory);
+ m_tagHistory = WTFMove(second);
+
+ m_selector = std::make_unique<CSSSelector>(tagQName, tagIsForNamespaceRule);
+ m_selector->setRelation(CSSSelector::Subselector);
+}
+
+std::unique_ptr<CSSParserSelector> CSSParserSelector::releaseTagHistory()
+{
+ setRelation(CSSSelector::Subselector);
+ return WTFMove(m_tagHistory);
+}
+
+// FIXME-NEWPARSER: Add support for :host-context
+bool CSSParserSelector::isHostPseudoSelector() const
+{
+ return match() == CSSSelector::PseudoClass && pseudoClassType() == CSSSelector::PseudoClassHost;
+}
+
+}
+
diff --git a/Source/WebCore/css/parser/CSSParserSelector.h b/Source/WebCore/css/parser/CSSParserSelector.h
new file mode 100644
index 000000000..a3d2e19d1
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserSelector.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2003 Lars Knoll (knoll@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2008, 2009, 2010, 2014 Apple Inc. 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.
+ */
+
+#pragma once
+
+#include "CSSSelector.h"
+#include "CSSValueKeywords.h"
+#include "CSSValueList.h"
+#include <wtf/text/AtomicString.h>
+#include <wtf/text/AtomicStringHash.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CSSValue;
+class QualifiedName;
+
+enum class CSSParserSelectorCombinator {
+ Child,
+ DescendantSpace,
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ DescendantDoubleChild,
+#endif
+ DirectAdjacent,
+ IndirectAdjacent
+};
+
+class CSSParserSelector {
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ static CSSParserSelector* parsePseudoClassSelectorFromStringView(StringView&);
+ static CSSParserSelector* parsePseudoElementSelectorFromStringView(StringView&);
+ static CSSParserSelector* parsePagePseudoSelector(const AtomicString&);
+
+ CSSParserSelector();
+ explicit CSSParserSelector(const QualifiedName&);
+ ~CSSParserSelector();
+
+ std::unique_ptr<CSSSelector> releaseSelector() { return WTFMove(m_selector); }
+
+ void setValue(const AtomicString& value, bool matchLowerCase = false) { m_selector->setValue(value, matchLowerCase); }
+
+ // FIXME-NEWPARSER: These two methods can go away once old parser is gone.
+ void setAttribute(const QualifiedName& value, bool isCaseInsensitive) { m_selector->setAttribute(value, isCaseInsensitive); }
+ void setAttributeValueMatchingIsCaseInsensitive(bool isCaseInsensitive) { m_selector->setAttributeValueMatchingIsCaseInsensitive(isCaseInsensitive); }
+
+ void setAttribute(const QualifiedName& value, bool convertToLowercase, CSSSelector::AttributeMatchType type) { m_selector->setAttribute(value, convertToLowercase, type); }
+
+ void setArgument(const AtomicString& value) { m_selector->setArgument(value); }
+ void setNth(int a, int b) { m_selector->setNth(a, b); }
+ void setMatch(CSSSelector::Match value) { m_selector->setMatch(value); }
+ void setRelation(CSSSelector::RelationType value) { m_selector->setRelation(value); }
+ void setForPage() { m_selector->setForPage(); }
+
+ CSSSelector::Match match() const { return m_selector->match(); }
+ CSSSelector::PseudoElementType pseudoElementType() const { return m_selector->pseudoElementType(); }
+ const CSSSelectorList* selectorList() const { return m_selector->selectorList(); }
+
+ void setPseudoElementType(CSSSelector::PseudoElementType type) { m_selector->setPseudoElementType(type); }
+
+ void adoptSelectorVector(Vector<std::unique_ptr<CSSParserSelector>>& selectorVector);
+ void setLangArgumentList(std::unique_ptr<Vector<AtomicString>>);
+ void setSelectorList(std::unique_ptr<CSSSelectorList>);
+
+ CSSSelector::PseudoClassType pseudoClassType() const { return m_selector->pseudoClassType(); }
+ bool isCustomPseudoElement() const { return m_selector->isCustomPseudoElement(); }
+
+ bool isPseudoElementCueFunction() const
+ {
+#if ENABLE(VIDEO_TRACK)
+ return m_selector->match() == CSSSelector::PseudoElement && m_selector->pseudoElementType() == CSSSelector::PseudoElementCue;
+#else
+ return false;
+#endif
+ }
+
+ bool hasShadowDescendant() const;
+ bool matchesPseudoElement() const;
+
+ bool isHostPseudoSelector() const;
+
+ // FIXME-NEWPARSER: "slotted" was removed here for now, since it leads to a combinator
+ // connection of ShadowDescendant, and the current shadow DOM code doesn't expect this. When
+ // we do fix this issue, make sure to patch the namespace prependTag code to remove the slotted
+ // special case, since it will be covered by this function once again.
+ bool needsImplicitShadowCombinatorForMatching() const;
+
+ CSSParserSelector* tagHistory() const { return m_tagHistory.get(); }
+ void setTagHistory(std::unique_ptr<CSSParserSelector> selector) { m_tagHistory = WTFMove(selector); }
+ void clearTagHistory() { m_tagHistory.reset(); }
+ void insertTagHistory(CSSSelector::RelationType before, std::unique_ptr<CSSParserSelector>, CSSSelector::RelationType after);
+ void appendTagHistory(CSSSelector::RelationType, std::unique_ptr<CSSParserSelector>);
+ void appendTagHistory(CSSParserSelectorCombinator, std::unique_ptr<CSSParserSelector>);
+ void prependTagSelector(const QualifiedName&, bool tagIsForNamespaceRule = false);
+ std::unique_ptr<CSSParserSelector> releaseTagHistory();
+
+private:
+ std::unique_ptr<CSSSelector> m_selector;
+ std::unique_ptr<CSSParserSelector> m_tagHistory;
+};
+
+inline bool CSSParserSelector::hasShadowDescendant() const
+{
+ return m_selector->relation() == CSSSelector::ShadowDescendant;
+}
+
+inline bool CSSParserSelector::needsImplicitShadowCombinatorForMatching() const
+{
+ return match() == CSSSelector::PseudoElement
+ && (pseudoElementType() == CSSSelector::PseudoElementWebKitCustom
+ || pseudoElementType() == CSSSelector::PseudoElementUserAgentCustom
+#if ENABLE(VIDEO_TRACK)
+ || pseudoElementType() == CSSSelector::PseudoElementCue
+#endif
+ || pseudoElementType() == CSSSelector::PseudoElementWebKitCustomLegacyPrefixed);
+}
+
+}
diff --git a/Source/WebCore/css/parser/CSSParserToken.cpp b/Source/WebCore/css/parser/CSSParserToken.cpp
new file mode 100644
index 000000000..3e95b5a49
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserToken.cpp
@@ -0,0 +1,479 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSParserToken.h"
+
+#include "CSSMarkup.h"
+#include "CSSPrimitiveValue.h"
+#include "CSSPropertyParser.h"
+#include <limits.h>
+#include <wtf/HashMap.h>
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+template<typename CharacterType>
+CSSPrimitiveValue::UnitType cssPrimitiveValueUnitFromTrie(const CharacterType* data, unsigned length)
+{
+ ASSERT(data);
+ ASSERT(length);
+ switch (length) {
+ case 1:
+ switch (toASCIILower(data[0])) {
+ case 's':
+ return CSSPrimitiveValue::UnitType::CSS_S;
+ }
+ break;
+ case 2:
+ switch (toASCIILower(data[0])) {
+ case 'c':
+ switch (toASCIILower(data[1])) {
+ case 'h':
+ return CSSPrimitiveValue::UnitType::CSS_CHS;
+ case 'm':
+ return CSSPrimitiveValue::UnitType::CSS_CM;
+ }
+ break;
+ case 'e':
+ switch (toASCIILower(data[1])) {
+ case 'm':
+ return CSSPrimitiveValue::UnitType::CSS_EMS;
+ case 'x':
+ return CSSPrimitiveValue::UnitType::CSS_EXS;
+ }
+ break;
+ case 'f':
+ if (toASCIILower(data[1]) == 'r')
+ return CSSPrimitiveValue::UnitType::CSS_FR;
+ break;
+ case 'h':
+ if (toASCIILower(data[1]) == 'z')
+ return CSSPrimitiveValue::UnitType::CSS_HZ;
+ break;
+ case 'i':
+ if (toASCIILower(data[1]) == 'n')
+ return CSSPrimitiveValue::UnitType::CSS_IN;
+ break;
+ case 'm':
+ switch (toASCIILower(data[1])) {
+ case 'm':
+ return CSSPrimitiveValue::UnitType::CSS_MM;
+ case 's':
+ return CSSPrimitiveValue::UnitType::CSS_MS;
+ }
+ break;
+ case 'p':
+ switch (toASCIILower(data[1])) {
+ case 'c':
+ return CSSPrimitiveValue::UnitType::CSS_PC;
+ case 't':
+ return CSSPrimitiveValue::UnitType::CSS_PT;
+ case 'x':
+ return CSSPrimitiveValue::UnitType::CSS_PX;
+ }
+ break;
+ case 'v':
+ switch (toASCIILower(data[1])) {
+ case 'h':
+ return CSSPrimitiveValue::UnitType::CSS_VH;
+ case 'w':
+ return CSSPrimitiveValue::UnitType::CSS_VW;
+ }
+ break;
+ }
+ break;
+ case 3:
+ switch (toASCIILower(data[0])) {
+ case 'd':
+ switch (toASCIILower(data[1])) {
+ case 'e':
+ if (toASCIILower(data[2]) == 'g')
+ return CSSPrimitiveValue::UnitType::CSS_DEG;
+ break;
+ case 'p':
+ if (toASCIILower(data[2]) == 'i')
+ return CSSPrimitiveValue::UnitType::CSS_DPI;
+ break;
+ }
+ break;
+ case 'k':
+ if (toASCIILower(data[1]) == 'h' && toASCIILower(data[2]) == 'z')
+ return CSSPrimitiveValue::UnitType::CSS_KHZ;
+ break;
+ case 'r':
+ switch (toASCIILower(data[1])) {
+ case 'a':
+ if (toASCIILower(data[2]) == 'd')
+ return CSSPrimitiveValue::UnitType::CSS_RAD;
+ break;
+ case 'e':
+ if (toASCIILower(data[2]) == 'm')
+ return CSSPrimitiveValue::UnitType::CSS_REMS;
+ break;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (toASCIILower(data[0])) {
+ case 'd':
+ switch (toASCIILower(data[1])) {
+ case 'p':
+ switch (toASCIILower(data[2])) {
+ case 'c':
+ if (toASCIILower(data[3]) == 'm')
+ return CSSPrimitiveValue::UnitType::CSS_DPCM;
+ break;
+ case 'p':
+ if (toASCIILower(data[3]) == 'x')
+ return CSSPrimitiveValue::UnitType::CSS_DPPX;
+ break;
+ }
+ break;
+ }
+ break;
+ case 'g':
+ if (toASCIILower(data[1]) == 'r' && toASCIILower(data[2]) == 'a' && toASCIILower(data[3]) == 'd')
+ return CSSPrimitiveValue::UnitType::CSS_GRAD;
+ break;
+ case 't':
+ if (toASCIILower(data[1]) == 'u' && toASCIILower(data[2]) == 'r' && toASCIILower(data[3]) == 'n')
+ return CSSPrimitiveValue::UnitType::CSS_TURN;
+ break;
+ case 'v':
+ switch (toASCIILower(data[1])) {
+ case 'm':
+ switch (toASCIILower(data[2])) {
+ case 'a':
+ if (toASCIILower(data[3]) == 'x')
+ return CSSPrimitiveValue::UnitType::CSS_VMAX;
+ break;
+ case 'i':
+ if (toASCIILower(data[3]) == 'n')
+ return CSSPrimitiveValue::UnitType::CSS_VMIN;
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ break;
+ case 5:
+ switch (toASCIILower(data[0])) {
+ case '_':
+ if (toASCIILower(data[1]) == '_' && toASCIILower(data[2]) == 'q' && toASCIILower(data[3]) == 'e' && toASCIILower(data[4]) == 'm')
+ return CSSPrimitiveValue::UnitType::CSS_QUIRKY_EMS;
+ break;
+ }
+ break;
+ }
+ return CSSPrimitiveValue::UnitType::CSS_UNKNOWN;
+}
+
+static CSSPrimitiveValue::UnitType stringToUnitType(StringView stringView)
+{
+ if (stringView.is8Bit())
+ return cssPrimitiveValueUnitFromTrie(stringView.characters8(), stringView.length());
+ return cssPrimitiveValueUnitFromTrie(stringView.characters16(), stringView.length());
+}
+
+CSSParserToken::CSSParserToken(CSSParserTokenType type, BlockType blockType)
+ : m_type(type)
+ , m_blockType(blockType)
+{
+}
+
+// Just a helper used for Delimiter tokens.
+CSSParserToken::CSSParserToken(CSSParserTokenType type, UChar c)
+ : m_type(type)
+ , m_blockType(NotBlock)
+ , m_delimiter(c)
+{
+ ASSERT(m_type == DelimiterToken);
+}
+
+CSSParserToken::CSSParserToken(CSSParserTokenType type, StringView value, BlockType blockType)
+ : m_type(type)
+ , m_blockType(blockType)
+{
+ initValueFromStringView(value);
+ m_id = -1;
+}
+
+CSSParserToken::CSSParserToken(CSSParserTokenType type, double numericValue, NumericValueType numericValueType, NumericSign sign)
+ : m_type(type)
+ , m_blockType(NotBlock)
+ , m_numericValueType(numericValueType)
+ , m_numericSign(sign)
+ , m_unit(static_cast<unsigned>(CSSPrimitiveValue::UnitType::CSS_NUMBER))
+{
+ ASSERT(type == NumberToken);
+ m_numericValue = numericValue;
+}
+
+CSSParserToken::CSSParserToken(CSSParserTokenType type, UChar32 start, UChar32 end)
+ : m_type(UnicodeRangeToken)
+ , m_blockType(NotBlock)
+{
+ ASSERT_UNUSED(type, type == UnicodeRangeToken);
+ m_unicodeRange.start = start;
+ m_unicodeRange.end = end;
+}
+
+CSSParserToken::CSSParserToken(HashTokenType type, StringView value)
+ : m_type(HashToken)
+ , m_blockType(NotBlock)
+ , m_hashTokenType(type)
+{
+ initValueFromStringView(value);
+}
+
+void CSSParserToken::convertToDimensionWithUnit(StringView unit)
+{
+ ASSERT(m_type == NumberToken);
+ m_type = DimensionToken;
+ initValueFromStringView(unit);
+ m_unit = static_cast<unsigned>(stringToUnitType(unit));
+}
+
+void CSSParserToken::convertToPercentage()
+{
+ ASSERT(m_type == NumberToken);
+ m_type = PercentageToken;
+ m_unit = static_cast<unsigned>(CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+}
+
+UChar CSSParserToken::delimiter() const
+{
+ ASSERT(m_type == DelimiterToken);
+ return m_delimiter;
+}
+
+NumericSign CSSParserToken::numericSign() const
+{
+ // This is valid for DimensionToken and PercentageToken, but only used
+ // in <an+b> parsing on NumberTokens.
+ ASSERT(m_type == NumberToken);
+ return static_cast<NumericSign>(m_numericSign);
+}
+
+NumericValueType CSSParserToken::numericValueType() const
+{
+ ASSERT(m_type == NumberToken || m_type == PercentageToken || m_type == DimensionToken);
+ return static_cast<NumericValueType>(m_numericValueType);
+}
+
+double CSSParserToken::numericValue() const
+{
+ ASSERT(m_type == NumberToken || m_type == PercentageToken || m_type == DimensionToken);
+ return m_numericValue;
+}
+
+CSSPropertyID CSSParserToken::parseAsCSSPropertyID() const
+{
+ ASSERT(m_type == IdentToken);
+ return cssPropertyID(value());
+}
+
+CSSValueID CSSParserToken::id() const
+{
+ if (m_type != IdentToken)
+ return CSSValueInvalid;
+ if (m_id < 0)
+ m_id = cssValueKeywordID(value());
+ return static_cast<CSSValueID>(m_id);
+}
+
+CSSValueID CSSParserToken::functionId() const
+{
+ if (m_type != FunctionToken)
+ return CSSValueInvalid;
+ if (m_id < 0)
+ m_id = cssValueKeywordID(value());
+ return static_cast<CSSValueID>(m_id);
+}
+
+bool CSSParserToken::hasStringBacking() const
+{
+ CSSParserTokenType tokenType = type();
+ return tokenType == IdentToken
+ || tokenType == FunctionToken
+ || tokenType == AtKeywordToken
+ || tokenType == HashToken
+ || tokenType == UrlToken
+ || tokenType == DimensionToken
+ || tokenType == StringToken;
+}
+
+CSSParserToken CSSParserToken::copyWithUpdatedString(const StringView& string) const
+{
+ CSSParserToken copy(*this);
+ copy.initValueFromStringView(string);
+ return copy;
+}
+
+bool CSSParserToken::valueDataCharRawEqual(const CSSParserToken& other) const
+{
+ if (m_valueLength != other.m_valueLength)
+ return false;
+
+ if (m_valueDataCharRaw == other.m_valueDataCharRaw && m_valueIs8Bit == other.m_valueIs8Bit)
+ return true;
+
+ if (m_valueIs8Bit)
+ return other.m_valueIs8Bit ? equal(static_cast<const LChar*>(m_valueDataCharRaw), static_cast<const LChar*>(other.m_valueDataCharRaw), m_valueLength) : equal(static_cast<const LChar*>(m_valueDataCharRaw), static_cast<const UChar*>(other.m_valueDataCharRaw), m_valueLength);
+
+ return other.m_valueIs8Bit ? equal(static_cast<const UChar*>(m_valueDataCharRaw), static_cast<const LChar*>(other.m_valueDataCharRaw), m_valueLength) : equal(static_cast<const UChar*>(m_valueDataCharRaw), static_cast<const UChar*>(other.m_valueDataCharRaw), m_valueLength);
+}
+
+bool CSSParserToken::operator==(const CSSParserToken& other) const
+{
+ if (m_type != other.m_type)
+ return false;
+ switch (m_type) {
+ case DelimiterToken:
+ return delimiter() == other.delimiter();
+ case HashToken:
+ if (m_hashTokenType != other.m_hashTokenType)
+ return false;
+ FALLTHROUGH;
+ case IdentToken:
+ case FunctionToken:
+ case StringToken:
+ case UrlToken:
+ return valueDataCharRawEqual(other);
+ case DimensionToken:
+ if (!valueDataCharRawEqual(other))
+ return false;
+ FALLTHROUGH;
+ case NumberToken:
+ case PercentageToken:
+ return m_numericSign == other.m_numericSign && m_numericValue == other.m_numericValue && m_numericValueType == other.m_numericValueType;
+ case UnicodeRangeToken:
+ return m_unicodeRange.start == other.m_unicodeRange.start && m_unicodeRange.end == other.m_unicodeRange.end;
+ default:
+ return true;
+ }
+}
+
+void CSSParserToken::serialize(StringBuilder& builder) const
+{
+ // This is currently only used for @supports CSSOM. To keep our implementation
+ // simple we handle some of the edge cases incorrectly (see comments below).
+ switch (type()) {
+ case IdentToken:
+ serializeIdentifier(value().toString(), builder);
+ break;
+ case FunctionToken:
+ serializeIdentifier(value().toString(), builder);
+ return builder.append('(');
+ case AtKeywordToken:
+ builder.append('@');
+ serializeIdentifier(value().toString(), builder);
+ break;
+ case HashToken:
+ builder.append('#');
+ serializeIdentifier(value().toString(), builder, (getHashTokenType() == HashTokenUnrestricted));
+ break;
+ case UrlToken:
+ builder.append("url(");
+ serializeIdentifier(value().toString(), builder);
+ return builder.append(')');
+ case DelimiterToken:
+ if (delimiter() == '\\')
+ return builder.append("\\\n");
+ return builder.append(delimiter());
+ case NumberToken:
+ // These won't properly preserve the NumericValueType flag
+ if (m_numericSign == PlusSign)
+ builder.append('+');
+ return builder.appendNumber(numericValue());
+ case PercentageToken:
+ builder.appendNumber(numericValue());
+ return builder.append('%');
+ case DimensionToken:
+ // This will incorrectly serialize e.g. 4e3e2 as 4000e2
+ builder.appendNumber(numericValue());
+ serializeIdentifier(value().toString(), builder);
+ break;
+ case UnicodeRangeToken:
+ return builder.append(String::format("U+%X-%X", unicodeRangeStart(), unicodeRangeEnd()));
+ case StringToken:
+ return serializeString(value().toString(), builder);
+
+ case IncludeMatchToken:
+ return builder.append("~=");
+ case DashMatchToken:
+ return builder.append("|=");
+ case PrefixMatchToken:
+ return builder.append("^=");
+ case SuffixMatchToken:
+ return builder.append("$=");
+ case SubstringMatchToken:
+ return builder.append("*=");
+ case ColumnToken:
+ return builder.append("||");
+ case CDOToken:
+ return builder.append("<!--");
+ case CDCToken:
+ return builder.append("-->");
+ case BadStringToken:
+ return builder.append("'\n");
+ case BadUrlToken:
+ return builder.append("url(()");
+ case WhitespaceToken:
+ return builder.append(' ');
+ case ColonToken:
+ return builder.append(':');
+ case SemicolonToken:
+ return builder.append(';');
+ case CommaToken:
+ return builder.append(',');
+ case LeftParenthesisToken:
+ return builder.append('(');
+ case RightParenthesisToken:
+ return builder.append(')');
+ case LeftBracketToken:
+ return builder.append('[');
+ case RightBracketToken:
+ return builder.append(']');
+ case LeftBraceToken:
+ return builder.append('{');
+ case RightBraceToken:
+ return builder.append('}');
+
+ case EOFToken:
+ case CommentToken:
+ ASSERT_NOT_REACHED();
+ return;
+ }
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserToken.h b/Source/WebCore/css/parser/CSSParserToken.h
new file mode 100644
index 000000000..7870c00f3
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserToken.h
@@ -0,0 +1,178 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSPrimitiveValue.h"
+#include <wtf/text/StringView.h>
+
+namespace WebCore {
+
+enum CSSParserTokenType {
+ IdentToken = 0,
+ FunctionToken,
+ AtKeywordToken,
+ HashToken,
+ UrlToken,
+ BadUrlToken,
+ DelimiterToken,
+ NumberToken,
+ PercentageToken,
+ DimensionToken,
+ IncludeMatchToken,
+ DashMatchToken,
+ PrefixMatchToken,
+ SuffixMatchToken,
+ SubstringMatchToken,
+ ColumnToken,
+ UnicodeRangeToken,
+ WhitespaceToken,
+ CDOToken,
+ CDCToken,
+ ColonToken,
+ SemicolonToken,
+ CommaToken,
+ LeftParenthesisToken,
+ RightParenthesisToken,
+ LeftBracketToken,
+ RightBracketToken,
+ LeftBraceToken,
+ RightBraceToken,
+ StringToken,
+ BadStringToken,
+ EOFToken,
+ CommentToken,
+};
+
+enum NumericSign {
+ NoSign,
+ PlusSign,
+ MinusSign,
+};
+
+enum NumericValueType {
+ IntegerValueType,
+ NumberValueType,
+};
+
+enum HashTokenType {
+ HashTokenId,
+ HashTokenUnrestricted,
+};
+
+class CSSParserToken {
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ enum BlockType {
+ NotBlock,
+ BlockStart,
+ BlockEnd,
+ };
+
+ CSSParserToken(CSSParserTokenType, BlockType = NotBlock);
+ CSSParserToken(CSSParserTokenType, StringView, BlockType = NotBlock);
+
+ CSSParserToken(CSSParserTokenType, UChar); // for DelimiterToken
+ CSSParserToken(CSSParserTokenType, double, NumericValueType, NumericSign); // for NumberToken
+ CSSParserToken(CSSParserTokenType, UChar32, UChar32); // for UnicodeRangeToken
+
+ CSSParserToken(HashTokenType, StringView);
+
+ bool operator==(const CSSParserToken& other) const;
+ bool operator!=(const CSSParserToken& other) const { return !(*this == other); }
+
+ // Converts NumberToken to DimensionToken.
+ void convertToDimensionWithUnit(StringView);
+
+ // Converts NumberToken to PercentageToken.
+ void convertToPercentage();
+
+ CSSParserTokenType type() const { return static_cast<CSSParserTokenType>(m_type); }
+ StringView value() const
+ {
+ if (m_valueIs8Bit)
+ return StringView(static_cast<const LChar*>(m_valueDataCharRaw), m_valueLength);
+ return StringView(static_cast<const UChar*>(m_valueDataCharRaw), m_valueLength);
+ }
+
+ UChar delimiter() const;
+ NumericSign numericSign() const;
+ NumericValueType numericValueType() const;
+ double numericValue() const;
+ HashTokenType getHashTokenType() const { ASSERT(m_type == HashToken); return m_hashTokenType; }
+ BlockType getBlockType() const { return static_cast<BlockType>(m_blockType); }
+ CSSPrimitiveValue::UnitType unitType() const { return static_cast<CSSPrimitiveValue::UnitType>(m_unit); }
+ UChar32 unicodeRangeStart() const { ASSERT(m_type == UnicodeRangeToken); return m_unicodeRange.start; }
+ UChar32 unicodeRangeEnd() const { ASSERT(m_type == UnicodeRangeToken); return m_unicodeRange.end; }
+ CSSValueID id() const;
+ CSSValueID functionId() const;
+
+ bool hasStringBacking() const;
+
+ CSSPropertyID parseAsCSSPropertyID() const;
+
+ void serialize(StringBuilder&) const;
+
+ CSSParserToken copyWithUpdatedString(const StringView&) const;
+
+private:
+ void initValueFromStringView(StringView string)
+ {
+ m_valueLength = string.length();
+ m_valueIs8Bit = string.is8Bit();
+ m_valueDataCharRaw = m_valueIs8Bit ? const_cast<void*>(static_cast<const void*>(string.characters8())) : const_cast<void*>(static_cast<const void*>(string.characters16()));
+ }
+ unsigned m_type : 6; // CSSParserTokenType
+ unsigned m_blockType : 2; // BlockType
+ unsigned m_numericValueType : 1; // NumericValueType
+ unsigned m_numericSign : 2; // NumericSign
+ unsigned m_unit : 7; // CSSPrimitiveValue::UnitType
+
+ bool valueDataCharRawEqual(const CSSParserToken& other) const;
+
+ // m_value... is an unpacked StringView so that we can pack it
+ // tightly with the rest of this object for a smaller object size.
+ bool m_valueIs8Bit : 1;
+ unsigned m_valueLength;
+ void* m_valueDataCharRaw; // Either LChar* or UChar*.
+
+ union {
+ UChar m_delimiter;
+ HashTokenType m_hashTokenType;
+ double m_numericValue;
+ mutable int m_id;
+
+ struct {
+ UChar32 start;
+ UChar32 end;
+ } m_unicodeRange;
+ };
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserTokenRange.cpp b/Source/WebCore/css/parser/CSSParserTokenRange.cpp
new file mode 100644
index 000000000..cb9c5807d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserTokenRange.cpp
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSParserTokenRange.h"
+
+#include "StyleSheetContents.h"
+#include <wtf/NeverDestroyed.h>
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+CSSParserToken& CSSParserTokenRange::eofToken()
+{
+ static NeverDestroyed<CSSParserToken> eofToken(EOFToken);
+ return eofToken.get();
+}
+
+CSSParserTokenRange CSSParserTokenRange::makeSubRange(const CSSParserToken* first, const CSSParserToken* last) const
+{
+ if (first == &eofToken())
+ first = m_last;
+ if (last == &eofToken())
+ last = m_last;
+ ASSERT(first <= last);
+ return CSSParserTokenRange(first, last);
+}
+
+CSSParserTokenRange CSSParserTokenRange::consumeBlock()
+{
+ ASSERT(peek().getBlockType() == CSSParserToken::BlockStart);
+ const CSSParserToken* start = &peek() + 1;
+ unsigned nestingLevel = 0;
+ do {
+ const CSSParserToken& token = consume();
+ if (token.getBlockType() == CSSParserToken::BlockStart)
+ nestingLevel++;
+ else if (token.getBlockType() == CSSParserToken::BlockEnd)
+ nestingLevel--;
+ } while (nestingLevel && m_first < m_last);
+
+ if (nestingLevel)
+ return makeSubRange(start, m_first); // Ended at EOF
+ return makeSubRange(start, m_first - 1);
+}
+
+CSSParserTokenRange CSSParserTokenRange::consumeBlockCheckingForEditability(StyleSheetContents* styleSheet)
+{
+ ASSERT(peek().getBlockType() == CSSParserToken::BlockStart);
+ const auto* start = &peek() + 1;
+ unsigned nestingLevel = 0;
+ do {
+ const auto& token = consume();
+ if (token.getBlockType() == CSSParserToken::BlockStart)
+ nestingLevel++;
+ else if (token.getBlockType() == CSSParserToken::BlockEnd)
+ nestingLevel--;
+
+ if (styleSheet && !styleSheet->usesStyleBasedEditability() && token.type() == IdentToken && equalLettersIgnoringASCIICase(token.value(), "-webkit-user-modify"))
+ styleSheet->parserSetUsesStyleBasedEditability();
+ } while (nestingLevel && m_first < m_last);
+
+ if (nestingLevel)
+ return makeSubRange(start, m_first); // Ended at EOF
+ return makeSubRange(start, m_first - 1);
+}
+
+void CSSParserTokenRange::consumeComponentValue()
+{
+ // FIXME: This is going to do multiple passes over large sections of a stylesheet.
+ // We should consider optimising this by precomputing where each block ends.
+ unsigned nestingLevel = 0;
+ do {
+ const CSSParserToken& token = consume();
+ if (token.getBlockType() == CSSParserToken::BlockStart)
+ nestingLevel++;
+ else if (token.getBlockType() == CSSParserToken::BlockEnd)
+ nestingLevel--;
+ } while (nestingLevel && m_first < m_last);
+}
+
+String CSSParserTokenRange::serialize() const
+{
+ // We're supposed to insert comments between certain pairs of token types
+ // as per spec, but since this is currently only used for @supports CSSOM
+ // we just get these cases wrong and avoid the additional complexity.
+ StringBuilder builder;
+ for (const CSSParserToken* it = m_first; it < m_last; ++it)
+ it->serialize(builder);
+ return builder.toString();
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSParserTokenRange.h b/Source/WebCore/css/parser/CSSParserTokenRange.h
new file mode 100644
index 000000000..8a9dd439d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSParserTokenRange.h
@@ -0,0 +1,104 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserToken.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+// A CSSParserTokenRange is an iterator over a subrange of a vector of CSSParserTokens.
+// Accessing outside of the range will return an endless stream of EOF tokens.
+// This class refers to half-open intervals [first, last).
+class CSSParserTokenRange {
+public:
+ template<size_t inlineBuffer>
+ CSSParserTokenRange(const Vector<CSSParserToken, inlineBuffer>& vector)
+ : m_first(vector.begin())
+ , m_last(vector.end())
+ {
+ }
+
+ // This should be called on a range with tokens returned by that range.
+ CSSParserTokenRange makeSubRange(const CSSParserToken* first, const CSSParserToken* last) const;
+
+ bool atEnd() const { return m_first == m_last; }
+ const CSSParserToken* end() const { return m_last; }
+
+ const CSSParserToken& peek(unsigned offset = 0) const
+ {
+ if (m_first + offset >= m_last)
+ return eofToken();
+ return *(m_first + offset);
+ }
+
+ const CSSParserToken& consume()
+ {
+ if (m_first == m_last)
+ return eofToken();
+ return *m_first++;
+ }
+
+ const CSSParserToken& consumeIncludingWhitespace()
+ {
+ const CSSParserToken& result = consume();
+ consumeWhitespace();
+ return result;
+ }
+
+ // The returned range doesn't include the brackets
+ CSSParserTokenRange consumeBlock();
+ CSSParserTokenRange consumeBlockCheckingForEditability(StyleSheetContents*);
+
+ void consumeComponentValue();
+
+ void consumeWhitespace()
+ {
+ while (peek().type() == WhitespaceToken)
+ ++m_first;
+ }
+
+ String serialize() const;
+
+ const CSSParserToken* begin() const { return m_first; }
+
+ static CSSParserToken& eofToken();
+
+private:
+ CSSParserTokenRange(const CSSParserToken* first, const CSSParserToken* last)
+ : m_first(first)
+ , m_last(last)
+ { }
+
+ const CSSParserToken* m_first;
+ const CSSParserToken* m_last;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSPropertyParser.cpp b/Source/WebCore/css/parser/CSSPropertyParser.cpp
new file mode 100644
index 000000000..129c2996a
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSPropertyParser.cpp
@@ -0,0 +1,5445 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSPropertyParser.h"
+
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+#include "CSSAnimationTriggerScrollValue.h"
+#endif
+#include "CSSAspectRatioValue.h"
+#include "CSSBasicShapes.h"
+#include "CSSBorderImage.h"
+#include "CSSBorderImageSliceValue.h"
+#include "CSSContentDistributionValue.h"
+#include "CSSCursorImageValue.h"
+#include "CSSCustomIdentValue.h"
+#include "CSSFontFaceSrcValue.h"
+#include "CSSFontFeatureValue.h"
+#if ENABLE(VARIATION_FONTS)
+#include "CSSFontVariationValue.h"
+#endif
+#include "CSSFunctionValue.h"
+#include "CSSGridAutoRepeatValue.h"
+#include "CSSGridLineNamesValue.h"
+#include "CSSGridTemplateAreasValue.h"
+#include "CSSInheritedValue.h"
+#include "CSSInitialValue.h"
+#include "CSSLineBoxContainValue.h"
+#include "CSSParserFastPaths.h"
+#include "CSSParserIdioms.h"
+#include "CSSPendingSubstitutionValue.h"
+#include "CSSPrimitiveValueMappings.h"
+#include "CSSPropertyParserHelpers.h"
+#include "CSSReflectValue.h"
+#include "CSSRevertValue.h"
+#include "CSSShadowValue.h"
+#include "CSSTimingFunctionValue.h"
+#include "CSSUnicodeRangeValue.h"
+#include "CSSUnsetValue.h"
+#include "CSSVariableParser.h"
+#include "CSSVariableReferenceValue.h"
+#include "Counter.h"
+#if ENABLE(DASHBOARD_SUPPORT)
+#include "DashboardRegion.h"
+#endif
+#include "FontFace.h"
+#include "HashTools.h"
+// FIXME-NEWPARSER: Replace Pair and Rect with actual CSSValue subclasses (CSSValuePair and CSSQuadValue).
+#include "Pair.h"
+#include "Rect.h"
+#include "RenderTheme.h"
+#include "RuntimeEnabledFeatures.h"
+#include "SVGPathByteStream.h"
+#include "SVGPathUtilities.h"
+#include "StylePropertyShorthand.h"
+#include "StylePropertyShorthandFunctions.h"
+#include <bitset>
+#include <memory>
+#include <wtf/text/StringBuilder.h>
+
+using namespace WTF;
+
+namespace WebCore {
+
+
+bool isCustomPropertyName(const String& propertyName)
+{
+ return propertyName.length() > 2 && propertyName.characterAt(0) == '-' && propertyName.characterAt(1) == '-';
+}
+
+static bool hasPrefix(const char* string, unsigned length, const char* prefix)
+{
+ for (unsigned i = 0; i < length; ++i) {
+ if (!prefix[i])
+ return true;
+ if (string[i] != prefix[i])
+ return false;
+ }
+ return false;
+}
+
+#if PLATFORM(IOS)
+void cssPropertyNameIOSAliasing(const char* propertyName, const char*& propertyNameAlias, unsigned& newLength)
+{
+ if (!strcmp(propertyName, "-webkit-hyphenate-locale")) {
+ // Worked in iOS 4.2.
+ static const char webkitLocale[] = "-webkit-locale";
+ propertyNameAlias = webkitLocale;
+ newLength = strlen(webkitLocale);
+ }
+}
+#endif
+
+template <typename CharacterType>
+static CSSPropertyID cssPropertyID(const CharacterType* propertyName, unsigned length)
+{
+ char buffer[maxCSSPropertyNameLength + 1 + 1]; // 1 to turn "apple"/"khtml" into "webkit", 1 for null character
+
+ for (unsigned i = 0; i != length; ++i) {
+ CharacterType c = propertyName[i];
+ if (!c || c >= 0x7F)
+ return CSSPropertyInvalid; // illegal character
+ buffer[i] = toASCIILower(c);
+ }
+ buffer[length] = '\0';
+
+ const char* name = buffer;
+ if (buffer[0] == '-') {
+#if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
+ // If the prefix is -apple- or -khtml-, change it to -webkit-.
+ // This makes the string one character longer.
+ if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled()
+ && (hasPrefix(buffer, length, "-apple-") || hasPrefix(buffer, length, "-khtml-"))) {
+ memmove(buffer + 7, buffer + 6, length + 1 - 6);
+ memcpy(buffer, "-webkit", 7);
+ ++length;
+ }
+#endif
+#if PLATFORM(IOS)
+ cssPropertyNameIOSAliasing(buffer, name, length);
+#endif
+ }
+
+ const Property* hashTableEntry = findProperty(name, length);
+ return hashTableEntry ? static_cast<CSSPropertyID>(hashTableEntry->id) : CSSPropertyInvalid;
+}
+
+static bool isAppleLegacyCssValueKeyword(const char* valueKeyword, unsigned length)
+{
+ static const char applePrefix[] = "-apple-";
+ static const char appleSystemPrefix[] = "-apple-system";
+ static const char applePayPrefix[] = "-apple-pay";
+ static const char* appleWirelessPlaybackTargetActive = getValueName(CSSValueAppleWirelessPlaybackTargetActive);
+
+ return hasPrefix(valueKeyword, length, applePrefix)
+ && !hasPrefix(valueKeyword, length, appleSystemPrefix)
+ && !hasPrefix(valueKeyword, length, applePayPrefix)
+ && !WTF::equal(reinterpret_cast<const LChar*>(valueKeyword), reinterpret_cast<const LChar*>(appleWirelessPlaybackTargetActive), length);
+}
+
+template <typename CharacterType>
+static CSSValueID cssValueKeywordID(const CharacterType* valueKeyword, unsigned length)
+{
+ char buffer[maxCSSValueKeywordLength + 1 + 1]; // 1 to turn "apple"/"khtml" into "webkit", 1 for null character
+
+ for (unsigned i = 0; i != length; ++i) {
+ CharacterType c = valueKeyword[i];
+ if (!c || c >= 0x7F)
+ return CSSValueInvalid; // illegal keyword.
+ buffer[i] = WTF::toASCIILower(c);
+ }
+ buffer[length] = '\0';
+
+ if (buffer[0] == '-') {
+ // If the prefix is -apple- or -khtml-, change it to -webkit-.
+ // This makes the string one character longer.
+ // On iOS we don't want to change values starting with -apple-system to -webkit-system.
+ // FIXME: Remove this mangling without breaking the web.
+ if (isAppleLegacyCssValueKeyword(buffer, length) || hasPrefix(buffer, length, "-khtml-")) {
+ memmove(buffer + 7, buffer + 6, length + 1 - 6);
+ memcpy(buffer, "-webkit", 7);
+ ++length;
+ }
+ }
+
+ const Value* hashTableEntry = findValue(buffer, length);
+ return hashTableEntry ? static_cast<CSSValueID>(hashTableEntry->id) : CSSValueInvalid;
+}
+
+CSSValueID cssValueKeywordID(StringView string)
+{
+ unsigned length = string.length();
+ if (!length)
+ return CSSValueInvalid;
+ if (length > maxCSSValueKeywordLength)
+ return CSSValueInvalid;
+
+ return string.is8Bit() ? cssValueKeywordID(string.characters8(), length) : cssValueKeywordID(string.characters16(), length);
+}
+
+CSSPropertyID cssPropertyID(StringView string)
+{
+ unsigned length = string.length();
+
+ if (!length)
+ return CSSPropertyInvalid;
+ if (length > maxCSSPropertyNameLength)
+ return CSSPropertyInvalid;
+
+ return string.is8Bit() ? cssPropertyID(string.characters8(), length) : cssPropertyID(string.characters16(), length);
+}
+
+using namespace CSSPropertyParserHelpers;
+
+CSSPropertyParser::CSSPropertyParser(const CSSParserTokenRange& range, const CSSParserContext& context, StyleSheetContents* styleSheetContents, Vector<CSSProperty, 256>* parsedProperties)
+ : m_range(range)
+ , m_context(context)
+ , m_styleSheetContents(styleSheetContents)
+ , m_parsedProperties(parsedProperties)
+{
+ m_range.consumeWhitespace();
+}
+
+void CSSPropertyParser::addProperty(CSSPropertyID property, CSSPropertyID currentShorthand, Ref<CSSValue>&& value, bool important, bool implicit)
+{
+ int shorthandIndex = 0;
+ bool setFromShorthand = false;
+
+ if (currentShorthand) {
+ auto shorthands = matchingShorthandsForLonghand(property);
+ setFromShorthand = true;
+ if (shorthands.size() > 1)
+ shorthandIndex = indexOfShorthandForLonghand(currentShorthand, shorthands);
+ }
+
+ m_parsedProperties->append(CSSProperty(property, WTFMove(value), important, setFromShorthand, shorthandIndex, implicit));
+}
+
+void CSSPropertyParser::addExpandedPropertyForValue(CSSPropertyID property, Ref<CSSValue>&& value, bool important)
+{
+ const StylePropertyShorthand& shorthand = shorthandForProperty(property);
+ unsigned shorthandLength = shorthand.length();
+ ASSERT(shorthandLength);
+ const CSSPropertyID* longhands = shorthand.properties();
+ for (unsigned i = 0; i < shorthandLength; ++i)
+ addProperty(longhands[i], property, value.copyRef(), important);
+}
+
+bool CSSPropertyParser::parseValue(CSSPropertyID propertyID, bool important, const CSSParserTokenRange& range, const CSSParserContext& context, StyleSheetContents* styleSheetContents, ParsedPropertyVector& parsedProperties, StyleRule::Type ruleType)
+{
+ int parsedPropertiesSize = parsedProperties.size();
+
+ CSSPropertyParser parser(range, context, styleSheetContents, &parsedProperties);
+ bool parseSuccess;
+
+#if ENABLE(CSS_DEVICE_ADAPTATION)
+ if (ruleType == StyleRule::Viewport)
+ parseSuccess = parser.parseViewportDescriptor(propertyID, important);
+ else
+#endif
+ if (ruleType == StyleRule::FontFace)
+ parseSuccess = parser.parseFontFaceDescriptor(propertyID);
+ else
+ parseSuccess = parser.parseValueStart(propertyID, important);
+
+ if (!parseSuccess)
+ parsedProperties.shrink(parsedPropertiesSize);
+
+ return parseSuccess;
+}
+
+RefPtr<CSSValue> CSSPropertyParser::parseSingleValue(CSSPropertyID property, const CSSParserTokenRange& range, const CSSParserContext& context, StyleSheetContents* styleSheetContents)
+{
+ CSSPropertyParser parser(range, context, styleSheetContents, nullptr);
+ RefPtr<CSSValue> value = parser.parseSingleValue(property);
+ if (!value || !parser.m_range.atEnd())
+ return nullptr;
+ return value;
+}
+
+static bool isLegacyBreakProperty(CSSPropertyID propertyID)
+{
+ switch (propertyID) {
+ case CSSPropertyPageBreakAfter:
+ case CSSPropertyPageBreakBefore:
+ case CSSPropertyPageBreakInside:
+ case CSSPropertyWebkitColumnBreakAfter:
+ case CSSPropertyWebkitColumnBreakBefore:
+ case CSSPropertyWebkitColumnBreakInside:
+#if ENABLE(CSS_REGIONS)
+ case CSSPropertyWebkitRegionBreakAfter:
+ case CSSPropertyWebkitRegionBreakBefore:
+ case CSSPropertyWebkitRegionBreakInside:
+#endif
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool CSSPropertyParser::parseValueStart(CSSPropertyID propertyID, bool important)
+{
+ if (consumeCSSWideKeyword(propertyID, important))
+ return true;
+
+ CSSParserTokenRange originalRange = m_range;
+ bool isShorthand = isShorthandCSSProperty(propertyID);
+
+ if (isShorthand) {
+ // Variable references will fail to parse here and will fall out to the variable ref parser below.
+ if (parseShorthand(propertyID, important))
+ return true;
+ } else if (isLegacyBreakProperty(propertyID)) {
+ // FIXME-NEWPARSER: Can turn this into a shorthand once old parser is gone, and then
+ // we don't need the special case.
+ if (consumeLegacyBreakProperty(propertyID, important))
+ return true;
+ } else {
+ RefPtr<CSSValue> parsedValue = parseSingleValue(propertyID);
+ if (parsedValue && m_range.atEnd()) {
+ addProperty(propertyID, CSSPropertyInvalid, *parsedValue, important);
+ return true;
+ }
+ }
+
+ if (CSSVariableParser::containsValidVariableReferences(originalRange)) {
+ RefPtr<CSSVariableReferenceValue> variable = CSSVariableReferenceValue::create(CSSVariableData::create(originalRange));
+
+ if (isShorthand) {
+ RefPtr<CSSPendingSubstitutionValue> pendingValue = CSSPendingSubstitutionValue::create(propertyID, variable.releaseNonNull());
+ addExpandedPropertyForValue(propertyID, pendingValue.releaseNonNull(), important);
+ } else
+ addProperty(propertyID, CSSPropertyInvalid, variable.releaseNonNull(), important);
+ return true;
+ }
+
+ return false;
+}
+
+bool CSSPropertyParser::consumeCSSWideKeyword(CSSPropertyID propertyID, bool important)
+{
+ CSSParserTokenRange rangeCopy = m_range;
+ CSSValueID valueID = rangeCopy.consumeIncludingWhitespace().id();
+ if (!rangeCopy.atEnd())
+ return false;
+
+ RefPtr<CSSValue> value;
+ if (valueID == CSSValueInherit)
+ value = CSSValuePool::singleton().createInheritedValue();
+ else if (valueID == CSSValueInitial)
+ value = CSSValuePool::singleton().createExplicitInitialValue();
+ else if (valueID == CSSValueUnset)
+ value = CSSValuePool::singleton().createUnsetValue();
+ else if (valueID == CSSValueRevert)
+ value = CSSValuePool::singleton().createRevertValue();
+ else
+ return false;
+
+ const StylePropertyShorthand& shorthand = shorthandForProperty(propertyID);
+ if (!shorthand.length()) {
+ if (CSSProperty::isDescriptorOnly(propertyID))
+ return false;
+ addProperty(propertyID, CSSPropertyInvalid, value.releaseNonNull(), important);
+ } else
+ addExpandedPropertyForValue(propertyID, value.releaseNonNull(), important);
+ m_range = rangeCopy;
+ return true;
+}
+
+bool CSSPropertyParser::consumeTransformOrigin(bool important)
+{
+ RefPtr<CSSPrimitiveValue> resultX;
+ RefPtr<CSSPrimitiveValue> resultY;
+ if (consumeOneOrTwoValuedPosition(m_range, m_context.mode, UnitlessQuirk::Forbid, resultX, resultY)) {
+ m_range.consumeWhitespace();
+ bool atEnd = m_range.atEnd();
+ RefPtr<CSSPrimitiveValue> resultZ = consumeLength(m_range, m_context.mode, ValueRangeAll);
+ bool hasZ = resultZ;
+ if (!hasZ && !atEnd)
+ return false;
+ addProperty(CSSPropertyTransformOriginX, CSSPropertyTransformOrigin, resultX.releaseNonNull(), important);
+ addProperty(CSSPropertyTransformOriginY, CSSPropertyTransformOrigin, resultY.releaseNonNull(), important);
+ addProperty(CSSPropertyTransformOriginZ, CSSPropertyTransformOrigin, resultZ ? resultZ.releaseNonNull() : CSSValuePool::singleton().createValue(0, CSSPrimitiveValue::UnitType::CSS_PX), important, !hasZ);
+
+ return true;
+ }
+ return false;
+}
+
+bool CSSPropertyParser::consumePerspectiveOrigin(bool important)
+{
+ RefPtr<CSSPrimitiveValue> resultX;
+ RefPtr<CSSPrimitiveValue> resultY;
+ if (consumePosition(m_range, m_context.mode, UnitlessQuirk::Forbid, resultX, resultY)) {
+ addProperty(CSSPropertyPerspectiveOriginX, CSSPropertyPerspectiveOrigin, resultX.releaseNonNull(), important);
+ addProperty(CSSPropertyPerspectiveOriginY, CSSPropertyPerspectiveOrigin, resultY.releaseNonNull(), important);
+ return true;
+ }
+ return false;
+}
+
+// Methods for consuming non-shorthand properties starts here.
+static RefPtr<CSSValue> consumeWillChange(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated();
+ // Every comma-separated list of identifiers is a valid will-change value,
+ // unless the list includes an explicitly disallowed identifier.
+ while (true) {
+ if (range.peek().type() != IdentToken)
+ return nullptr;
+ CSSPropertyID propertyID = cssPropertyID(range.peek().value());
+ if (propertyID != CSSPropertyInvalid) {
+ // Now "all" is used by both CSSValue and CSSPropertyValue.
+ // Need to return nullptr when currentValue is CSSPropertyAll.
+ if (propertyID == CSSPropertyWillChange || propertyID == CSSPropertyAll)
+ return nullptr;
+ // FIXME-NEWPARSER: Use CSSCustomIdentValue someday.
+ values->append(CSSValuePool::singleton().createIdentifierValue(propertyID));
+ range.consumeIncludingWhitespace();
+ } else {
+ switch (range.peek().id()) {
+ case CSSValueNone:
+ case CSSValueAll:
+ case CSSValueAuto:
+ case CSSValueDefault:
+ case CSSValueInitial:
+ case CSSValueInherit:
+ return nullptr;
+ case CSSValueContents:
+ case CSSValueScrollPosition:
+ values->append(consumeIdent(range).releaseNonNull());
+ break;
+ default:
+ // Append properties we don't recognize, but that are legal, as strings.
+ values->append(consumeCustomIdent(range).releaseNonNull());
+ break;
+ }
+ }
+
+ if (range.atEnd())
+ break;
+ if (!consumeCommaIncludingWhitespace(range))
+ return nullptr;
+ }
+
+ return values;
+}
+
+static RefPtr<CSSFontFeatureValue> consumeFontFeatureTag(CSSParserTokenRange& range)
+{
+ // Feature tag name consists of 4-letter characters.
+ static const unsigned tagNameLength = 4;
+
+ const CSSParserToken& token = range.consumeIncludingWhitespace();
+ // Feature tag name comes first
+ if (token.type() != StringToken)
+ return nullptr;
+ if (token.value().length() != tagNameLength)
+ return nullptr;
+
+ FontTag tag;
+ for (unsigned i = 0; i < tag.size(); ++i) {
+ // Limits the range of characters to 0x20-0x7E, following the tag name rules defiend in the OpenType specification.
+ UChar character = token.value()[i];
+ if (character < 0x20 || character > 0x7E)
+ return nullptr;
+ tag[i] = toASCIILower(character);
+ }
+
+ int tagValue = 1;
+ // Feature tag values could follow: <integer> | on | off
+ if (range.peek().type() == NumberToken && range.peek().numericValueType() == IntegerValueType && range.peek().numericValue() >= 0) {
+ tagValue = clampTo<int>(range.consumeIncludingWhitespace().numericValue());
+ if (tagValue < 0)
+ return nullptr;
+ } else if (range.peek().id() == CSSValueOn || range.peek().id() == CSSValueOff) {
+ tagValue = range.consumeIncludingWhitespace().id() == CSSValueOn;
+ }
+ return CSSFontFeatureValue::create(WTFMove(tag), tagValue);
+}
+
+static RefPtr<CSSValue> consumeFontFeatureSettings(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+ RefPtr<CSSValueList> settings = CSSValueList::createCommaSeparated();
+ do {
+ RefPtr<CSSFontFeatureValue> fontFeatureValue = consumeFontFeatureTag(range);
+ if (!fontFeatureValue)
+ return nullptr;
+ settings->append(fontFeatureValue.releaseNonNull());
+ } while (consumeCommaIncludingWhitespace(range));
+ return settings;
+}
+
+#if ENABLE(VARIATION_FONTS)
+static RefPtr<CSSValue> consumeFontVariationTag(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != StringToken)
+ return nullptr;
+
+ auto string = range.consumeIncludingWhitespace().value().toString();
+
+ FontTag tag;
+ if (string.length() != tag.size())
+ return nullptr;
+ for (unsigned i = 0; i < tag.size(); ++i) {
+ // Limits the range of characters to 0x20-0x7E, following the tag name rules defiend in the OpenType specification.
+ UChar character = string[i];
+ if (character < 0x20 || character > 0x7E)
+ return nullptr;
+ tag[i] = character;
+ }
+
+ if (range.atEnd() || range.peek().type() != NumberToken)
+ return nullptr;
+
+ float tagValue = range.consumeIncludingWhitespace().numericValue();
+
+ return CSSFontVariationValue::create(tag, tagValue);
+}
+
+static RefPtr<CSSValue> consumeFontVariationSettings(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+
+ auto settings = CSSValueList::createCommaSeparated();
+ do {
+ RefPtr<CSSValue> variationValue = consumeFontVariationTag(range);
+ if (!variationValue)
+ return nullptr;
+ settings->append(variationValue.releaseNonNull());
+ } while (consumeCommaIncludingWhitespace(range));
+
+ if (!settings->length())
+ return nullptr;
+
+ return WTFMove(settings);
+}
+#endif // ENABLE(VARIATION_FONTS)
+
+static RefPtr<CSSValue> consumePage(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ return consumeCustomIdent(range);
+}
+
+static RefPtr<CSSValue> consumeQuotes(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ RefPtr<CSSValueList> values = CSSValueList::createSpaceSeparated();
+ while (!range.atEnd()) {
+ RefPtr<CSSPrimitiveValue> parsedValue = consumeString(range);
+ if (!parsedValue)
+ return nullptr;
+ values->append(parsedValue.releaseNonNull());
+ }
+ if (values->length() && values->length() % 2 == 0)
+ return values;
+ return nullptr;
+}
+
+class FontVariantLigaturesParser {
+public:
+ FontVariantLigaturesParser()
+ : m_sawCommonLigaturesValue(false)
+ , m_sawDiscretionaryLigaturesValue(false)
+ , m_sawHistoricalLigaturesValue(false)
+ , m_sawContextualLigaturesValue(false)
+ , m_result(CSSValueList::createSpaceSeparated())
+ {
+ }
+
+ enum class ParseResult {
+ ConsumedValue,
+ DisallowedValue,
+ UnknownValue
+ };
+
+ ParseResult consumeLigature(CSSParserTokenRange& range)
+ {
+ CSSValueID valueID = range.peek().id();
+ switch (valueID) {
+ case CSSValueNoCommonLigatures:
+ case CSSValueCommonLigatures:
+ if (m_sawCommonLigaturesValue)
+ return ParseResult::DisallowedValue;
+ m_sawCommonLigaturesValue = true;
+ break;
+ case CSSValueNoDiscretionaryLigatures:
+ case CSSValueDiscretionaryLigatures:
+ if (m_sawDiscretionaryLigaturesValue)
+ return ParseResult::DisallowedValue;
+ m_sawDiscretionaryLigaturesValue = true;
+ break;
+ case CSSValueNoHistoricalLigatures:
+ case CSSValueHistoricalLigatures:
+ if (m_sawHistoricalLigaturesValue)
+ return ParseResult::DisallowedValue;
+ m_sawHistoricalLigaturesValue = true;
+ break;
+ case CSSValueNoContextual:
+ case CSSValueContextual:
+ if (m_sawContextualLigaturesValue)
+ return ParseResult::DisallowedValue;
+ m_sawContextualLigaturesValue = true;
+ break;
+ default:
+ return ParseResult::UnknownValue;
+ }
+ m_result->append(consumeIdent(range).releaseNonNull());
+ return ParseResult::ConsumedValue;
+ }
+
+ RefPtr<CSSValue> finalizeValue()
+ {
+ if (!m_result->length())
+ return CSSValuePool::singleton().createIdentifierValue(CSSValueNormal);
+ return m_result.release();
+ }
+
+private:
+ bool m_sawCommonLigaturesValue;
+ bool m_sawDiscretionaryLigaturesValue;
+ bool m_sawHistoricalLigaturesValue;
+ bool m_sawContextualLigaturesValue;
+ RefPtr<CSSValueList> m_result;
+};
+
+static RefPtr<CSSValue> consumeFontVariantLigatures(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNormal || range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ FontVariantLigaturesParser ligaturesParser;
+ do {
+ if (ligaturesParser.consumeLigature(range) !=
+ FontVariantLigaturesParser::ParseResult::ConsumedValue)
+ return nullptr;
+ } while (!range.atEnd());
+
+ return ligaturesParser.finalizeValue();
+}
+
+static RefPtr<CSSValue> consumeFontVariantEastAsian(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> values = CSSValueList::createSpaceSeparated();
+ FontVariantEastAsianVariant variant = FontVariantEastAsianVariant::Normal;
+ FontVariantEastAsianWidth width = FontVariantEastAsianWidth::Normal;
+ FontVariantEastAsianRuby ruby = FontVariantEastAsianRuby::Normal;
+
+ while (!range.atEnd()) {
+ if (range.peek().type() != IdentToken)
+ return nullptr;
+
+ auto id = range.peek().id();
+
+ switch (id) {
+ case CSSValueJis78:
+ variant = FontVariantEastAsianVariant::Jis78;
+ break;
+ case CSSValueJis83:
+ variant = FontVariantEastAsianVariant::Jis83;
+ break;
+ case CSSValueJis90:
+ variant = FontVariantEastAsianVariant::Jis90;
+ break;
+ case CSSValueJis04:
+ variant = FontVariantEastAsianVariant::Jis04;
+ break;
+ case CSSValueSimplified:
+ variant = FontVariantEastAsianVariant::Simplified;
+ break;
+ case CSSValueTraditional:
+ variant = FontVariantEastAsianVariant::Traditional;
+ break;
+ case CSSValueFullWidth:
+ width = FontVariantEastAsianWidth::Full;
+ break;
+ case CSSValueProportionalWidth:
+ width = FontVariantEastAsianWidth::Proportional;
+ break;
+ case CSSValueRuby:
+ ruby = FontVariantEastAsianRuby::Yes;
+ break;
+ default:
+ return nullptr;
+ }
+
+ range.consumeIncludingWhitespace();
+ }
+
+ switch (variant) {
+ case FontVariantEastAsianVariant::Normal:
+ break;
+ case FontVariantEastAsianVariant::Jis78:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis78));
+ break;
+ case FontVariantEastAsianVariant::Jis83:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis83));
+ break;
+ case FontVariantEastAsianVariant::Jis90:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis90));
+ break;
+ case FontVariantEastAsianVariant::Jis04:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis04));
+ break;
+ case FontVariantEastAsianVariant::Simplified:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueSimplified));
+ break;
+ case FontVariantEastAsianVariant::Traditional:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueTraditional));
+ break;
+ }
+
+ switch (width) {
+ case FontVariantEastAsianWidth::Normal:
+ break;
+ case FontVariantEastAsianWidth::Full:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueFullWidth));
+ break;
+ case FontVariantEastAsianWidth::Proportional:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueProportionalWidth));
+ break;
+ }
+
+ switch (ruby) {
+ case FontVariantEastAsianRuby::Normal:
+ break;
+ case FontVariantEastAsianRuby::Yes:
+ values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueRuby));
+ }
+
+ if (!values->length())
+ return nullptr;
+
+ return values;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeFontVariantCaps(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueNormal, CSSValueSmallCaps, CSSValueAllSmallCaps,
+ CSSValuePetiteCaps, CSSValueAllPetiteCaps,
+ CSSValueUnicase, CSSValueTitlingCaps>(range);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeFontVariantAlternates(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueNormal, CSSValueHistoricalForms>(range);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeFontVariantPosition(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueNormal, CSSValueSub, CSSValueSuper>(range);
+}
+
+class FontVariantNumericParser {
+public:
+ FontVariantNumericParser()
+ : m_sawNumericFigureValue(false)
+ , m_sawNumericSpacingValue(false)
+ , m_sawNumericFractionValue(false)
+ , m_sawOrdinalValue(false)
+ , m_sawSlashedZeroValue(false)
+ , m_result(CSSValueList::createSpaceSeparated())
+ {
+ }
+
+ enum class ParseResult {
+ ConsumedValue,
+ DisallowedValue,
+ UnknownValue
+ };
+
+ ParseResult consumeNumeric(CSSParserTokenRange& range)
+ {
+ CSSValueID valueID = range.peek().id();
+ switch (valueID) {
+ case CSSValueLiningNums:
+ case CSSValueOldstyleNums:
+ if (m_sawNumericFigureValue)
+ return ParseResult::DisallowedValue;
+ m_sawNumericFigureValue = true;
+ break;
+ case CSSValueProportionalNums:
+ case CSSValueTabularNums:
+ if (m_sawNumericSpacingValue)
+ return ParseResult::DisallowedValue;
+ m_sawNumericSpacingValue = true;
+ break;
+ case CSSValueDiagonalFractions:
+ case CSSValueStackedFractions:
+ if (m_sawNumericFractionValue)
+ return ParseResult::DisallowedValue;
+ m_sawNumericFractionValue = true;
+ break;
+ case CSSValueOrdinal:
+ if (m_sawOrdinalValue)
+ return ParseResult::DisallowedValue;
+ m_sawOrdinalValue = true;
+ break;
+ case CSSValueSlashedZero:
+ if (m_sawSlashedZeroValue)
+ return ParseResult::DisallowedValue;
+ m_sawSlashedZeroValue = true;
+ break;
+ default:
+ return ParseResult::UnknownValue;
+ }
+ m_result->append(consumeIdent(range).releaseNonNull());
+ return ParseResult::ConsumedValue;
+ }
+
+ RefPtr<CSSValue> finalizeValue()
+ {
+ if (!m_result->length())
+ return CSSValuePool::singleton().createIdentifierValue(CSSValueNormal);
+ return m_result.release();
+ }
+
+
+private:
+ bool m_sawNumericFigureValue;
+ bool m_sawNumericSpacingValue;
+ bool m_sawNumericFractionValue;
+ bool m_sawOrdinalValue;
+ bool m_sawSlashedZeroValue;
+ RefPtr<CSSValueList> m_result;
+};
+
+static RefPtr<CSSValue> consumeFontVariantNumeric(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+
+ FontVariantNumericParser numericParser;
+ do {
+ if (numericParser.consumeNumeric(range) !=
+ FontVariantNumericParser::ParseResult::ConsumedValue)
+ return nullptr;
+ } while (!range.atEnd());
+
+ return numericParser.finalizeValue();
+}
+
+static RefPtr<CSSPrimitiveValue> consumeFontVariantCSS21(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueNormal, CSSValueSmallCaps>(range);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeFontWeight(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.id() >= CSSValueNormal && token.id() <= CSSValueLighter)
+ return consumeIdent(range);
+ int weight;
+ if (!consumePositiveIntegerRaw(range, weight))
+ return nullptr;
+ if ((weight % 100) || weight < 100 || weight > 900)
+ return nullptr;
+ return CSSValuePool::singleton().createIdentifierValue(static_cast<CSSValueID>(CSSValue100 + weight / 100 - 1));
+}
+
+static String concatenateFamilyName(CSSParserTokenRange& range)
+{
+ StringBuilder builder;
+ bool addedSpace = false;
+ const CSSParserToken& firstToken = range.peek();
+ while (range.peek().type() == IdentToken) {
+ if (!builder.isEmpty()) {
+ builder.append(' ');
+ addedSpace = true;
+ }
+ builder.append(range.consumeIncludingWhitespace().value());
+ }
+ if (!addedSpace && isCSSWideKeyword(firstToken.id()))
+ return String();
+ return builder.toString();
+}
+
+static RefPtr<CSSValue> consumeFamilyName(CSSParserTokenRange& range)
+{
+ if (range.peek().type() == StringToken)
+ return CSSValuePool::singleton().createFontFamilyValue(range.consumeIncludingWhitespace().value().toString());
+ if (range.peek().type() != IdentToken)
+ return nullptr;
+ String familyName = concatenateFamilyName(range);
+ if (familyName.isNull())
+ return nullptr;
+ return CSSValuePool::singleton().createFontFamilyValue(familyName);
+}
+
+static RefPtr<CSSValue> consumeGenericFamily(CSSParserTokenRange& range)
+{
+ return consumeIdentRange(range, CSSValueSerif, CSSValueWebkitBody);
+}
+
+static RefPtr<CSSValueList> consumeFontFamily(CSSParserTokenRange& range)
+{
+ RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated();
+ do {
+ RefPtr<CSSValue> parsedValue = consumeGenericFamily(range);
+ if (parsedValue) {
+ list->append(parsedValue.releaseNonNull());
+ } else {
+ parsedValue = consumeFamilyName(range);
+ if (parsedValue) {
+ list->append(parsedValue.releaseNonNull());
+ } else {
+ return nullptr;
+ }
+ }
+ } while (consumeCommaIncludingWhitespace(range));
+ return list;
+}
+
+static RefPtr<CSSValueList> consumeFontFamilyDescriptor(CSSParserTokenRange& range)
+{
+ // FIXME-NEWPARSER: For compatibility with the old parser, we have to make
+ // a list here, even though the list always contains only a single family name.
+ // Once the old parser is gone, we can delete this function, make the caller
+ // use consumeFamilyName instead, and then patch the @font-face code to
+ // not expect a list with a single name in it.
+ RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated();
+ RefPtr<CSSValue> parsedValue = consumeFamilyName(range);
+ if (parsedValue)
+ list->append(parsedValue.releaseNonNull());
+
+ if (!range.atEnd() || !list->length())
+ return nullptr;
+
+ return list;
+}
+
+static RefPtr<CSSValue> consumeFontSynthesis(CSSParserTokenRange& range)
+{
+ // none | [ weight || style || small-caps ]
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ while (true) {
+ auto ident = consumeIdent<CSSValueWeight, CSSValueStyle, CSSValueSmallCaps>(range);
+ if (!ident)
+ break;
+ if (list->hasValue(ident.get()))
+ return nullptr;
+ list->append(ident.releaseNonNull());
+ }
+
+ if (!list->length())
+ return nullptr;
+ return list;
+}
+
+static RefPtr<CSSValue> consumeLetterSpacing(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+
+ return consumeLength(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumeWordSpacing(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumeTabSize(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ RefPtr<CSSPrimitiveValue> parsedValue = consumeInteger(range, 0);
+ if (parsedValue)
+ return parsedValue;
+ return consumeLength(range, cssParserMode, ValueRangeNonNegative);
+}
+
+#if ENABLE(TEXT_AUTOSIZING)
+static RefPtr<CSSValue> consumeTextSizeAdjust(CSSParserTokenRange& range, CSSParserMode /* cssParserMode */)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ return consumePercent(range, ValueRangeNonNegative);
+}
+#endif
+
+static RefPtr<CSSValue> consumeFontSize(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless = UnitlessQuirk::Forbid)
+{
+ if (range.peek().id() >= CSSValueXxSmall && range.peek().id() <= CSSValueLarger)
+ return consumeIdent(range);
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative, unitless);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeLineHeight(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+
+ RefPtr<CSSPrimitiveValue> lineHeight = consumeNumber(range, ValueRangeNonNegative);
+ if (lineHeight)
+ return lineHeight;
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative);
+}
+
+template<typename... Args>
+static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
+{
+ return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
+}
+
+static RefPtr<CSSValue> consumeCounter(CSSParserTokenRange& range, int defaultValue)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ do {
+ RefPtr<CSSPrimitiveValue> counterName = consumeCustomIdent(range);
+ if (!counterName)
+ return nullptr;
+ int i = defaultValue;
+ if (RefPtr<CSSPrimitiveValue> counterValue = consumeInteger(range))
+ i = counterValue->intValue();
+ list->append(createPrimitiveValuePair(counterName.releaseNonNull(), CSSPrimitiveValue::create(i, CSSPrimitiveValue::UnitType::CSS_NUMBER), Pair::IdenticalValueEncoding::Coalesce));
+ } while (!range.atEnd());
+ return list;
+}
+
+static RefPtr<CSSValue> consumePageSize(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueA3, CSSValueA4, CSSValueA5, CSSValueB4, CSSValueB5, CSSValueLedger, CSSValueLegal, CSSValueLetter>(range);
+}
+
+static RefPtr<CSSValueList> consumeSize(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ RefPtr<CSSValueList> result = CSSValueList::createSpaceSeparated();
+
+ if (range.peek().id() == CSSValueAuto) {
+ result->append(consumeIdent(range).releaseNonNull());
+ return result;
+ }
+
+ if (RefPtr<CSSValue> width = consumeLength(range, cssParserMode, ValueRangeNonNegative)) {
+ RefPtr<CSSValue> height = consumeLength(range, cssParserMode, ValueRangeNonNegative);
+ result->append(width.releaseNonNull());
+ if (height)
+ result->append(height.releaseNonNull());
+ return result;
+ }
+
+ RefPtr<CSSValue> pageSize = consumePageSize(range);
+ RefPtr<CSSValue> orientation = consumeIdent<CSSValuePortrait, CSSValueLandscape>(range);
+ if (!pageSize)
+ pageSize = consumePageSize(range);
+
+ if (!orientation && !pageSize)
+ return nullptr;
+ if (pageSize)
+ result->append(pageSize.releaseNonNull());
+ if (orientation)
+ result->append(orientation.releaseNonNull());
+ return result;
+}
+
+static RefPtr<CSSValue> consumeTextIndent(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ // [ <length> | <percentage> ] && hanging? && each-line?
+ // Keywords only allowed when css3Text is enabled.
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+
+ bool hasLengthOrPercentage = false;
+// bool hasEachLine = false;
+ bool hasHanging = false;
+
+ do {
+ if (!hasLengthOrPercentage) {
+ if (RefPtr<CSSValue> textIndent = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow)) {
+ list->append(*textIndent);
+ hasLengthOrPercentage = true;
+ continue;
+ }
+ }
+
+ CSSValueID id = range.peek().id();
+ /* FIXME-NEWPARSER: We don't support this yet.
+ if (!hasEachLine && id == CSSValueEachLine) {
+ list->append(*consumeIdent(range));
+ hasEachLine = true;
+ continue;
+ }
+*/
+
+ if (!hasHanging && id == CSSValueHanging) {
+ list->append(consumeIdent(range).releaseNonNull());
+ hasHanging = true;
+ continue;
+ }
+
+ return nullptr;
+ } while (!range.atEnd());
+
+ if (!hasLengthOrPercentage)
+ return nullptr;
+
+ return list;
+}
+
+// FIXME-NEWPARSER: Drop the prefix on min-content, max-content and fit-content.
+static bool validWidthOrHeightKeyword(CSSValueID id, const CSSParserContext& /*context*/)
+{
+ if (id == CSSValueIntrinsic || id == CSSValueMinIntrinsic || id == CSSValueWebkitMinContent || id == CSSValueWebkitMaxContent || id == CSSValueWebkitFillAvailable || id == CSSValueWebkitFitContent) {
+ return true;
+ }
+ return false;
+}
+
+static RefPtr<CSSValue> consumeMaxWidthOrHeight(CSSParserTokenRange& range, const CSSParserContext& context, UnitlessQuirk unitless = UnitlessQuirk::Forbid)
+{
+ if (range.peek().id() == CSSValueNone || validWidthOrHeightKeyword(range.peek().id(), context))
+ return consumeIdent(range);
+ return consumeLengthOrPercent(range, context.mode, ValueRangeNonNegative, unitless);
+}
+
+static RefPtr<CSSValue> consumeWidthOrHeight(CSSParserTokenRange& range, const CSSParserContext& context, UnitlessQuirk unitless = UnitlessQuirk::Forbid)
+{
+ if (range.peek().id() == CSSValueAuto || validWidthOrHeightKeyword(range.peek().id(), context))
+ return consumeIdent(range);
+ return consumeLengthOrPercent(range, context.mode, ValueRangeNonNegative, unitless);
+}
+
+static RefPtr<CSSValue> consumeMarginOrOffset(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeClipComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ return consumeLength(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumeClip(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+
+ if (range.peek().functionId() != CSSValueRect)
+ return nullptr;
+
+ CSSParserTokenRange args = consumeFunction(range);
+ // rect(t, r, b, l) || rect(t r b l)
+ RefPtr<CSSPrimitiveValue> top = consumeClipComponent(args, cssParserMode);
+ if (!top)
+ return nullptr;
+ bool needsComma = consumeCommaIncludingWhitespace(args);
+ RefPtr<CSSPrimitiveValue> right = consumeClipComponent(args, cssParserMode);
+ if (!right || (needsComma && !consumeCommaIncludingWhitespace(args)))
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> bottom = consumeClipComponent(args, cssParserMode);
+ if (!bottom || (needsComma && !consumeCommaIncludingWhitespace(args)))
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> left = consumeClipComponent(args, cssParserMode);
+ if (!left || !args.atEnd())
+ return nullptr;
+
+ auto rect = Rect::create();
+ rect->setLeft(left.releaseNonNull());
+ rect->setTop(top.releaseNonNull());
+ rect->setRight(right.releaseNonNull());
+ rect->setBottom(bottom.releaseNonNull());
+ return CSSValuePool::singleton().createValue(WTFMove(rect));
+}
+
+#if ENABLE(TOUCH_EVENTS)
+static RefPtr<CSSValue> consumeTouchAction(CSSParserTokenRange& range)
+{
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueAuto || id == CSSValueNone || id == CSSValueManipulation) {
+ list->append(consumeIdent(range).releaseNonNull());
+ return list;
+ }
+ // FIXME-NEWPARSER: Support pan.
+ return nullptr;
+}
+#endif
+
+static RefPtr<CSSPrimitiveValue> consumeLineClamp(CSSParserTokenRange& range)
+{
+ RefPtr<CSSPrimitiveValue> clampValue = consumePercent(range, ValueRangeNonNegative);
+ if (clampValue)
+ return clampValue;
+ // When specifying number of lines, don't allow 0 as a valid value.
+ return consumePositiveInteger(range);
+}
+
+static RefPtr<CSSValue> consumeLocale(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ return consumeString(range);
+}
+
+static RefPtr<CSSValue> consumeHyphenateLimit(CSSParserTokenRange& range, CSSValueID valueID)
+{
+ if (range.peek().id() == valueID)
+ return consumeIdent(range);
+ return consumeNumber(range, ValueRangeNonNegative);
+}
+
+static RefPtr<CSSValue> consumeColumnWidth(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ // Always parse lengths in strict mode here, since it would be ambiguous otherwise when used in
+ // the 'columns' shorthand property.
+ RefPtr<CSSPrimitiveValue> columnWidth = consumeLength(range, HTMLStandardMode, ValueRangeNonNegative);
+ if (!columnWidth || (!columnWidth->isCalculated() && !columnWidth->doubleValue()) || (columnWidth->cssCalcValue() && !columnWidth->cssCalcValue()->doubleValue()))
+ return nullptr;
+ return columnWidth;
+}
+
+static RefPtr<CSSValue> consumeColumnCount(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ return consumePositiveInteger(range);
+}
+
+static RefPtr<CSSValue> consumeColumnGap(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+ return consumeLength(range, cssParserMode, ValueRangeNonNegative);
+}
+
+static RefPtr<CSSValue> consumeColumnSpan(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueAll, CSSValueNone>(range);
+}
+
+static RefPtr<CSSValue> consumeZoom(CSSParserTokenRange& range, const CSSParserContext& /*context*/)
+{
+ const CSSParserToken& token = range.peek();
+ RefPtr<CSSPrimitiveValue> zoom;
+ if (token.type() == IdentToken)
+ zoom = consumeIdent<CSSValueNormal, CSSValueReset, CSSValueDocument>(range);
+ else {
+ zoom = consumePercent(range, ValueRangeNonNegative);
+ if (!zoom)
+ zoom = consumeNumber(range, ValueRangeNonNegative);
+ }
+ return zoom;
+}
+
+static RefPtr<CSSValue> consumeAnimationIterationCount(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueInfinite)
+ return consumeIdent(range);
+ return consumeNumber(range, ValueRangeNonNegative);
+}
+
+static RefPtr<CSSValue> consumeAnimationName(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ if (range.peek().type() == StringToken) {
+ const CSSParserToken& token = range.consumeIncludingWhitespace();
+ if (equalIgnoringASCIICase(token.value(), "none"))
+ return CSSValuePool::singleton().createIdentifierValue(CSSValueNone);
+ // FIXME-NEWPARSER: Want to use a CSSCustomIdentValue here eventually.
+ return CSSValuePool::singleton().createValue(token.value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
+ }
+
+ return consumeCustomIdent(range);
+}
+
+static RefPtr<CSSValue> consumeTransitionProperty(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() != IdentToken)
+ return nullptr;
+ if (token.id() == CSSValueNone)
+ return consumeIdent(range);
+
+ if (CSSPropertyID property = token.parseAsCSSPropertyID()) {
+ range.consumeIncludingWhitespace();
+
+ // FIXME-NEWPARSER: No reason why we can't use the "all" property now that it exists.
+ // The old parser used a value keyword for "all", though, since it predated support for
+ // the property.
+ if (property == CSSPropertyAll)
+ return CSSValuePool::singleton().createIdentifierValue(CSSValueAll);
+
+ // FIXME-NEWPARSER: Want to use a CSSCustomIdentValue here eventually.
+ return CSSValuePool::singleton().createIdentifierValue(property);
+ }
+ return consumeCustomIdent(range);
+}
+
+
+static RefPtr<CSSValue> consumeSteps(CSSParserTokenRange& range) {
+ ASSERT(range.peek().functionId() == CSSValueSteps);
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+
+ RefPtr<CSSPrimitiveValue> steps = consumePositiveInteger(args);
+ if (!steps)
+ return nullptr;
+
+ // FIXME-NEWPARSER: Support the middle value and change from a boolean to an enum.
+ bool stepAtStart = false;
+ if (consumeCommaIncludingWhitespace(args)) {
+ switch (args.consumeIncludingWhitespace().id()) {
+ case CSSValueStart:
+ stepAtStart = true;
+ break;
+ case CSSValueEnd:
+ stepAtStart = false;
+ break;
+ default:
+ return nullptr;
+ }
+ }
+
+ if (!args.atEnd())
+ return nullptr;
+
+ range = rangeCopy;
+ return CSSStepsTimingFunctionValue::create(steps->intValue(), stepAtStart);
+}
+
+static RefPtr<CSSValue> consumeCubicBezier(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().functionId() == CSSValueCubicBezier);
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+
+ double x1, y1, x2, y2;
+ if (consumeNumberRaw(args, x1)
+ && x1 >= 0 && x1 <= 1
+ && consumeCommaIncludingWhitespace(args)
+ && consumeNumberRaw(args, y1)
+ && consumeCommaIncludingWhitespace(args)
+ && consumeNumberRaw(args, x2)
+ && x2 >= 0 && x2 <= 1
+ && consumeCommaIncludingWhitespace(args)
+ && consumeNumberRaw(args, y2)
+ && args.atEnd()) {
+ range = rangeCopy;
+ return CSSCubicBezierTimingFunctionValue::create(x1, y1, x2, y2);
+ }
+
+ return nullptr;
+}
+
+static RefPtr<CSSValue> consumeSpringFunction(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().functionId() == CSSValueSpring);
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+
+ // Mass must be greater than 0.
+ double mass;
+ if (!consumeNumberRaw(args, mass) || mass <= 0)
+ return nullptr;
+
+ // Stiffness must be greater than 0.
+ double stiffness;
+ if (!consumeNumberRaw(args, stiffness) || stiffness <= 0)
+ return nullptr;
+
+ // Damping coefficient must be greater than or equal to 0.
+ double damping;
+ if (!consumeNumberRaw(args, damping) || damping < 0)
+ return nullptr;
+
+ // Initial velocity may have any value.
+ double initialVelocity;
+ if (!consumeNumberRaw(args, initialVelocity))
+ return nullptr;
+
+ if (!args.atEnd())
+ return nullptr;
+
+ range = rangeCopy;
+
+ return CSSSpringTimingFunctionValue::create(mass, stiffness, damping, initialVelocity);
+}
+
+static RefPtr<CSSValue> consumeAnimationTimingFunction(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueEase || id == CSSValueLinear || id == CSSValueEaseIn
+ || id == CSSValueEaseOut || id == CSSValueEaseInOut || id == CSSValueStepStart || id == CSSValueStepEnd)
+ return consumeIdent(range);
+
+ CSSValueID function = range.peek().functionId();
+ if (function == CSSValueCubicBezier)
+ return consumeCubicBezier(range);
+ if (function == CSSValueSteps)
+ return consumeSteps(range);
+ if (context.springTimingFunctionEnabled && function == CSSValueSpring)
+ return consumeSpringFunction(range);
+ return nullptr;
+}
+
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+static RefPtr<CSSValue> consumeWebkitAnimationTrigger(CSSParserTokenRange& range, CSSParserMode mode)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+
+ if (range.peek().functionId() != CSSValueContainerScroll)
+ return nullptr;
+
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+
+ RefPtr<CSSPrimitiveValue> startValue = consumeLength(args, mode, ValueRangeAll, UnitlessQuirk::Forbid);
+ if (!startValue)
+ return nullptr;
+
+ if (args.atEnd()) {
+ range = rangeCopy;
+ return CSSAnimationTriggerScrollValue::create(startValue.releaseNonNull());
+ }
+
+ if (!consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> endValue = consumeLength(args, mode, ValueRangeAll, UnitlessQuirk::Forbid);
+ if (!endValue || !args.atEnd())
+ return nullptr;
+
+ range = rangeCopy;
+
+ return CSSAnimationTriggerScrollValue::create(startValue.releaseNonNull(), endValue.releaseNonNull());
+}
+#endif
+
+static RefPtr<CSSValue> consumeAnimationValue(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ switch (property) {
+ case CSSPropertyAnimationDelay:
+ case CSSPropertyTransitionDelay:
+ return consumeTime(range, context.mode, ValueRangeAll, UnitlessQuirk::Forbid);
+ case CSSPropertyAnimationDirection:
+ return consumeIdent<CSSValueNormal, CSSValueAlternate, CSSValueReverse, CSSValueAlternateReverse>(range);
+ case CSSPropertyAnimationDuration:
+ case CSSPropertyTransitionDuration:
+ return consumeTime(range, context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow);
+ case CSSPropertyAnimationFillMode:
+ return consumeIdent<CSSValueNone, CSSValueForwards, CSSValueBackwards, CSSValueBoth>(range);
+ case CSSPropertyAnimationIterationCount:
+ return consumeAnimationIterationCount(range);
+ case CSSPropertyAnimationName:
+ return consumeAnimationName(range);
+ case CSSPropertyAnimationPlayState:
+ return consumeIdent<CSSValueRunning, CSSValuePaused>(range);
+ case CSSPropertyTransitionProperty:
+ return consumeTransitionProperty(range);
+ case CSSPropertyAnimationTimingFunction:
+ case CSSPropertyTransitionTimingFunction:
+ return consumeAnimationTimingFunction(range, context);
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+ case CSSPropertyWebkitAnimationTrigger:
+ return consumeWebkitAnimationTrigger(range, context.mode);
+#endif
+ default:
+ ASSERT_NOT_REACHED();
+ return nullptr;
+ }
+}
+
+static bool isValidAnimationPropertyList(CSSPropertyID property, const CSSValueList& valueList)
+{
+ if (property != CSSPropertyTransitionProperty || valueList.length() < 2)
+ return true;
+ for (auto& value : valueList) {
+ if (value->isPrimitiveValue() && downcast<CSSPrimitiveValue>(value.get()).isValueID()
+ && downcast<CSSPrimitiveValue>(value.get()).valueID() == CSSValueNone)
+ return false;
+ }
+ return true;
+}
+
+static RefPtr<CSSValue> consumeAnimationPropertyList(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ RefPtr<CSSValueList> list;
+ RefPtr<CSSValue> singleton;
+ do {
+ RefPtr<CSSValue> currentValue = consumeAnimationValue(property, range, context);
+ if (!currentValue)
+ return nullptr;
+
+ if (singleton && !list) {
+ list = CSSValueList::createCommaSeparated();
+ list->append(singleton.releaseNonNull());
+ }
+
+ if (list)
+ list->append(currentValue.releaseNonNull());
+ else
+ singleton = WTFMove(currentValue);
+
+ } while (consumeCommaIncludingWhitespace(range));
+
+ if (list) {
+ if (!isValidAnimationPropertyList(property, *list))
+ return nullptr;
+
+ ASSERT(list->length());
+ return list;
+ }
+
+ return singleton;
+}
+
+bool CSSPropertyParser::consumeAnimationShorthand(const StylePropertyShorthand& shorthand, bool important)
+{
+ const unsigned longhandCount = shorthand.length();
+ RefPtr<CSSValueList> longhands[8];
+ ASSERT(longhandCount <= 8);
+ for (size_t i = 0; i < longhandCount; ++i)
+ longhands[i] = CSSValueList::createCommaSeparated();
+
+ do {
+ bool parsedLonghand[8] = { false };
+ do {
+ bool foundProperty = false;
+ for (size_t i = 0; i < longhandCount; ++i) {
+ if (parsedLonghand[i])
+ continue;
+
+ if (RefPtr<CSSValue> value = consumeAnimationValue(shorthand.properties()[i], m_range, m_context)) {
+ parsedLonghand[i] = true;
+ foundProperty = true;
+ longhands[i]->append(*value);
+ break;
+ }
+ }
+ if (!foundProperty)
+ return false;
+ } while (!m_range.atEnd() && m_range.peek().type() != CommaToken);
+
+ // FIXME: This will make invalid longhands, see crbug.com/386459
+ for (size_t i = 0; i < longhandCount; ++i) {
+ if (!parsedLonghand[i])
+ longhands[i]->append(CSSValuePool::singleton().createImplicitInitialValue());
+ parsedLonghand[i] = false;
+ }
+ } while (consumeCommaIncludingWhitespace(m_range));
+
+ for (size_t i = 0; i < longhandCount; ++i) {
+ if (!isValidAnimationPropertyList(shorthand.properties()[i], *longhands[i]))
+ return false;
+ }
+
+ for (size_t i = 0; i < longhandCount; ++i)
+ addProperty(shorthand.properties()[i], shorthand.id(), *longhands[i], important);
+
+ return m_range.atEnd();
+}
+
+static RefPtr<CSSValue> consumeZIndex(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ return consumeInteger(range);
+}
+
+static RefPtr<CSSValue> consumeShadow(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool isBoxShadowProperty)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> shadowValueList = CSSValueList::createCommaSeparated();
+ do {
+ if (RefPtr<CSSShadowValue> shadowValue = consumeSingleShadow(range, cssParserMode, isBoxShadowProperty, isBoxShadowProperty))
+ shadowValueList->append(*shadowValue);
+ else
+ return nullptr;
+ } while (consumeCommaIncludingWhitespace(range));
+ return shadowValueList;
+}
+
+static RefPtr<CSSValue> consumeTextDecorationLine(CSSParserTokenRange& range)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ while (true) {
+#if ENABLE(LETTERPRESS)
+ RefPtr<CSSPrimitiveValue> ident = consumeIdent<CSSValueBlink, CSSValueUnderline, CSSValueOverline, CSSValueLineThrough, CSSValueWebkitLetterpress>(range);
+#else
+ RefPtr<CSSPrimitiveValue> ident = consumeIdent<CSSValueBlink, CSSValueUnderline, CSSValueOverline, CSSValueLineThrough>(range);
+#endif
+ if (!ident)
+ break;
+ if (list->hasValue(ident.get()))
+ return nullptr;
+ list->append(ident.releaseNonNull());
+ }
+
+ if (!list->length())
+ return nullptr;
+ return list;
+}
+
+static RefPtr<CSSValue> consumeTextDecorationSkip(CSSParserTokenRange& range)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ while (true) {
+ auto ident = consumeIdent<CSSValueAuto, CSSValueInk, CSSValueObjects>(range);
+ if (!ident)
+ break;
+ if (list->hasValue(ident.get()))
+ return nullptr;
+ list->append(ident.releaseNonNull());
+ }
+
+ if (!list->length())
+ return nullptr;
+ return list;
+}
+
+static RefPtr<CSSValue> consumeTextEmphasisStyle(CSSParserTokenRange& range)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueNone)
+ return consumeIdent(range);
+
+ if (RefPtr<CSSValue> textEmphasisStyle = consumeString(range))
+ return textEmphasisStyle;
+
+ RefPtr<CSSPrimitiveValue> fill = consumeIdent<CSSValueFilled, CSSValueOpen>(range);
+ RefPtr<CSSPrimitiveValue> shape = consumeIdent<CSSValueDot, CSSValueCircle, CSSValueDoubleCircle, CSSValueTriangle, CSSValueSesame>(range);
+ if (!fill)
+ fill = consumeIdent<CSSValueFilled, CSSValueOpen>(range);
+ if (fill && shape) {
+ RefPtr<CSSValueList> parsedValues = CSSValueList::createSpaceSeparated();
+ parsedValues->append(fill.releaseNonNull());
+ parsedValues->append(shape.releaseNonNull());
+ return parsedValues;
+ }
+ if (fill)
+ return fill;
+ if (shape)
+ return shape;
+ return nullptr;
+}
+
+static RefPtr<CSSValue> consumeOutlineColor(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ // Allow the special focus color even in HTML Standard parsing mode.
+ if (range.peek().id() == CSSValueWebkitFocusRingColor)
+ return consumeIdent(range);
+ return consumeColor(range, cssParserMode);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeLineWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueThin || id == CSSValueMedium || id == CSSValueThick)
+ return consumeIdent(range);
+ return consumeLength(range, cssParserMode, ValueRangeNonNegative, unitless);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeBorderWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ return consumeLineWidth(range, cssParserMode, unitless);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeTextStrokeWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ return consumeLineWidth(range, cssParserMode, UnitlessQuirk::Forbid);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeColumnRuleWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ return consumeLineWidth(range, cssParserMode, UnitlessQuirk::Forbid);
+}
+
+static bool consumeTranslate3d(CSSParserTokenRange& args, CSSParserMode cssParserMode, RefPtr<CSSFunctionValue>& transformValue)
+{
+ unsigned numberOfArguments = 2;
+ RefPtr<CSSValue> parsedValue;
+ do {
+ parsedValue = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (!parsedValue)
+ return false;
+ transformValue->append(*parsedValue);
+ if (!consumeCommaIncludingWhitespace(args))
+ return false;
+ } while (--numberOfArguments);
+ parsedValue = consumeLength(args, cssParserMode, ValueRangeAll);
+ if (!parsedValue)
+ return false;
+ transformValue->append(*parsedValue);
+ return true;
+}
+
+static bool consumeNumbers(CSSParserTokenRange& args, RefPtr<CSSFunctionValue>& transformValue, unsigned numberOfArguments)
+{
+ do {
+ RefPtr<CSSPrimitiveValue> parsedValue = consumeNumber(args, ValueRangeAll);
+ if (!parsedValue)
+ return false;
+ transformValue->append(parsedValue.releaseNonNull());
+ if (--numberOfArguments && !consumeCommaIncludingWhitespace(args))
+ return false;
+ } while (numberOfArguments);
+ return true;
+}
+
+static bool consumePerspective(CSSParserTokenRange& args, CSSParserMode cssParserMode, RefPtr<CSSFunctionValue>& transformValue)
+{
+ RefPtr<CSSPrimitiveValue> parsedValue = consumeLength(args, cssParserMode, ValueRangeNonNegative);
+ if (!parsedValue) {
+ double perspective;
+ if (!consumeNumberRaw(args, perspective) || perspective < 0)
+ return false;
+ parsedValue = CSSPrimitiveValue::create(perspective, CSSPrimitiveValue::UnitType::CSS_PX);
+ }
+ if (!parsedValue)
+ return false;
+ transformValue->append(parsedValue.releaseNonNull());
+ return true;
+}
+
+static RefPtr<CSSValue> consumeTransformValue(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ CSSValueID functionId = range.peek().functionId();
+ if (functionId == CSSValueInvalid)
+ return nullptr;
+ CSSParserTokenRange args = consumeFunction(range);
+ if (args.atEnd())
+ return nullptr;
+
+ RefPtr<CSSFunctionValue> transformValue = CSSFunctionValue::create(functionId);
+ RefPtr<CSSValue> parsedValue;
+ switch (functionId) {
+ case CSSValueRotate:
+ case CSSValueRotateX:
+ case CSSValueRotateY:
+ case CSSValueRotateZ:
+ case CSSValueSkewX:
+ case CSSValueSkewY:
+ case CSSValueSkew:
+ parsedValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
+ if (!parsedValue)
+ return nullptr;
+ if (functionId == CSSValueSkew && consumeCommaIncludingWhitespace(args)) {
+ transformValue->append(*parsedValue);
+ parsedValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
+ if (!parsedValue)
+ return nullptr;
+ }
+ break;
+ case CSSValueScaleX:
+ case CSSValueScaleY:
+ case CSSValueScaleZ:
+ case CSSValueScale:
+ parsedValue = consumeNumber(args, ValueRangeAll);
+ if (!parsedValue)
+ return nullptr;
+ if (functionId == CSSValueScale && consumeCommaIncludingWhitespace(args)) {
+ transformValue->append(*parsedValue);
+ parsedValue = consumeNumber(args, ValueRangeAll);
+ if (!parsedValue)
+ return nullptr;
+ }
+ break;
+ case CSSValuePerspective:
+ if (!consumePerspective(args, cssParserMode, transformValue))
+ return nullptr;
+ break;
+ case CSSValueTranslateX:
+ case CSSValueTranslateY:
+ case CSSValueTranslate:
+ parsedValue = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (!parsedValue)
+ return nullptr;
+ if (functionId == CSSValueTranslate && consumeCommaIncludingWhitespace(args)) {
+ transformValue->append(*parsedValue);
+ parsedValue = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (!parsedValue)
+ return nullptr;
+ }
+ break;
+ case CSSValueTranslateZ:
+ parsedValue = consumeLength(args, cssParserMode, ValueRangeAll);
+ break;
+ case CSSValueMatrix:
+ case CSSValueMatrix3d:
+ if (!consumeNumbers(args, transformValue, (functionId == CSSValueMatrix3d) ? 16 : 6))
+ return nullptr;
+ break;
+ case CSSValueScale3d:
+ if (!consumeNumbers(args, transformValue, 3))
+ return nullptr;
+ break;
+ case CSSValueRotate3d:
+ if (!consumeNumbers(args, transformValue, 3) || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ parsedValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
+ if (!parsedValue)
+ return nullptr;
+ break;
+ case CSSValueTranslate3d:
+ if (!consumeTranslate3d(args, cssParserMode, transformValue))
+ return nullptr;
+ break;
+ default:
+ return nullptr;
+ }
+ if (parsedValue)
+ transformValue->append(*parsedValue);
+ if (!args.atEnd())
+ return nullptr;
+ return transformValue;
+}
+
+static RefPtr<CSSValue> consumeTransform(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ do {
+ RefPtr<CSSValue> parsedTransformValue = consumeTransformValue(range, cssParserMode);
+ if (!parsedTransformValue)
+ return nullptr;
+ list->append(parsedTransformValue.releaseNonNull());
+ } while (!range.atEnd());
+
+ return list;
+}
+
+template <CSSValueID start, CSSValueID end>
+static RefPtr<CSSPrimitiveValue> consumePositionLonghand(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().type() == IdentToken) {
+ CSSValueID id = range.peek().id();
+ int percent;
+ if (id == start)
+ percent = 0;
+ else if (id == CSSValueCenter)
+ percent = 50;
+ else if (id == end)
+ percent = 100;
+ else
+ return nullptr;
+ range.consumeIncludingWhitespace();
+ return CSSPrimitiveValue::create(percent, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ }
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
+}
+
+static RefPtr<CSSPrimitiveValue> consumePositionX(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ return consumePositionLonghand<CSSValueLeft, CSSValueRight>(range, cssParserMode);
+}
+
+static RefPtr<CSSPrimitiveValue> consumePositionY(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ return consumePositionLonghand<CSSValueTop, CSSValueBottom>(range, cssParserMode);
+}
+
+static RefPtr<CSSValue> consumePaintStroke(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ RefPtr<CSSPrimitiveValue> url = consumeUrl(range);
+ if (url) {
+ RefPtr<CSSValue> parsedValue;
+ if (range.peek().id() == CSSValueNone)
+ parsedValue = consumeIdent(range);
+ else
+ parsedValue = consumeColor(range, cssParserMode);
+ if (parsedValue) {
+ RefPtr<CSSValueList> values = CSSValueList::createSpaceSeparated();
+ values->append(url.releaseNonNull());
+ values->append(parsedValue.releaseNonNull());
+ return values;
+ }
+ return url;
+ }
+ return consumeColor(range, cssParserMode);
+}
+
+static RefPtr<CSSValue> consumeGlyphOrientation(CSSParserTokenRange& range, CSSParserMode mode, CSSPropertyID property)
+{
+ if (range.peek().id() == CSSValueAuto) {
+ if (property == CSSPropertyGlyphOrientationVertical)
+ return consumeIdent(range);
+ return nullptr;
+ }
+
+ return consumeAngle(range, mode, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumePaintOrder(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNormal)
+ return consumeIdent(range);
+
+ Vector<CSSValueID, 3> paintTypeList;
+ RefPtr<CSSPrimitiveValue> fill;
+ RefPtr<CSSPrimitiveValue> stroke;
+ RefPtr<CSSPrimitiveValue> markers;
+ do {
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueFill && !fill)
+ fill = consumeIdent(range);
+ else if (id == CSSValueStroke && !stroke)
+ stroke = consumeIdent(range);
+ else if (id == CSSValueMarkers && !markers)
+ markers = consumeIdent(range);
+ else
+ return nullptr;
+ paintTypeList.append(id);
+ } while (!range.atEnd());
+
+ // After parsing we serialize the paint-order list. Since it is not possible to
+ // pop a last list items from CSSValueList without bigger cost, we create the
+ // list after parsing.
+ CSSValueID firstPaintOrderType = paintTypeList.at(0);
+ RefPtr<CSSValueList> paintOrderList = CSSValueList::createSpaceSeparated();
+ switch (firstPaintOrderType) {
+ case CSSValueFill:
+ case CSSValueStroke:
+ paintOrderList->append(firstPaintOrderType == CSSValueFill ? fill.releaseNonNull() : stroke.releaseNonNull());
+ if (paintTypeList.size() > 1) {
+ if (paintTypeList.at(1) == CSSValueMarkers)
+ paintOrderList->append(markers.releaseNonNull());
+ }
+ break;
+ case CSSValueMarkers:
+ paintOrderList->append(markers.releaseNonNull());
+ if (paintTypeList.size() > 1) {
+ if (paintTypeList.at(1) == CSSValueStroke)
+ paintOrderList->append(stroke.releaseNonNull());
+ }
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ return paintOrderList;
+}
+
+static RefPtr<CSSValue> consumeNoneOrURI(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ return consumeUrl(range);
+}
+
+static RefPtr<CSSValue> consumeFlexBasis(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ // FIXME: Support intrinsic dimensions too.
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative);
+}
+
+static RefPtr<CSSValue> consumeKerning(CSSParserTokenRange& range, CSSParserMode mode)
+{
+ RefPtr<CSSValue> result = consumeIdent<CSSValueAuto, CSSValueNormal>(range);
+ if (result)
+ return result;
+ return consumeLength(range, mode, ValueRangeAll, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumeStrokeDasharray(CSSParserTokenRange& range)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> dashes = CSSValueList::createCommaSeparated();
+ do {
+ RefPtr<CSSPrimitiveValue> dash = consumeLengthOrPercent(range, SVGAttributeMode, ValueRangeNonNegative);
+ if (!dash || (consumeCommaIncludingWhitespace(range) && range.atEnd()))
+ return nullptr;
+ dashes->append(dash.releaseNonNull());
+ } while (!range.atEnd());
+ return dashes;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeBaselineShift(CSSParserTokenRange& range)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueBaseline || id == CSSValueSub || id == CSSValueSuper)
+ return consumeIdent(range);
+ return consumeLengthOrPercent(range, SVGAttributeMode, ValueRangeAll);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeRxOrRy(CSSParserTokenRange& range)
+{
+ // FIXME-NEWPARSER: We don't support auto values when mapping, so for now turn this
+ // off until we can figure out if we're even supposed to support it.
+ // if (range.peek().id() == CSSValueAuto)
+ // return consumeIdent(range);
+ return consumeLengthOrPercent(range, SVGAttributeMode, ValueRangeAll, UnitlessQuirk::Forbid);
+}
+
+static RefPtr<CSSValue> consumeCursor(CSSParserTokenRange& range, const CSSParserContext& context, bool inQuirksMode)
+{
+ RefPtr<CSSValueList> list;
+ while (RefPtr<CSSValue> image = consumeImage(range, context, ConsumeGeneratedImage::Forbid)) {
+ double num;
+ IntPoint hotSpot(-1, -1);
+ bool hotSpotSpecified = false;
+ if (consumeNumberRaw(range, num)) {
+ hotSpot.setX(int(num));
+ if (!consumeNumberRaw(range, num))
+ return nullptr;
+ hotSpot.setY(int(num));
+ hotSpotSpecified = true;
+ }
+
+ if (!list)
+ list = CSSValueList::createCommaSeparated();
+
+ list->append(CSSCursorImageValue::create(image.releaseNonNull(), hotSpotSpecified, hotSpot));
+ if (!consumeCommaIncludingWhitespace(range))
+ return nullptr;
+ }
+
+ CSSValueID id = range.peek().id();
+ RefPtr<CSSValue> cursorType;
+ if (id == CSSValueHand) {
+ if (!inQuirksMode) // Non-standard behavior
+ return nullptr;
+ cursorType = CSSValuePool::singleton().createIdentifierValue(CSSValuePointer);
+ range.consumeIncludingWhitespace();
+ } else if ((id >= CSSValueAuto && id <= CSSValueWebkitZoomOut) || id == CSSValueCopy || id == CSSValueNone) {
+ cursorType = consumeIdent(range);
+ } else {
+ return nullptr;
+ }
+
+ if (!list)
+ return cursorType;
+ list->append(cursorType.releaseNonNull());
+ return list;
+}
+
+static RefPtr<CSSValue> consumeAttr(CSSParserTokenRange args, CSSParserContext context)
+{
+ if (args.peek().type() != IdentToken)
+ return nullptr;
+
+ CSSParserToken token = args.consumeIncludingWhitespace();
+ auto attrName = token.value().toAtomicString();
+ if (context.isHTMLDocument)
+ attrName = attrName.convertToASCIILowercase();
+
+ if (!args.atEnd())
+ return nullptr;
+
+ // FIXME-NEWPARSER: We want to use a CSSCustomIdentValue here eventually for the attrName.
+ // FIXME-NEWPARSER: We want to use a CSSFunctionValue rather than relying on a custom
+ // attr() primitive value.
+ return CSSValuePool::singleton().createValue(attrName, CSSPrimitiveValue::CSS_ATTR);
+}
+
+static RefPtr<CSSValue> consumeCounterContent(CSSParserTokenRange args, bool counters)
+{
+ RefPtr<CSSPrimitiveValue> identifier = consumeCustomIdent(args);
+ if (!identifier)
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> separator;
+ if (!counters)
+ separator = CSSPrimitiveValue::create(String(), CSSPrimitiveValue::UnitType::CSS_STRING);
+ else {
+ if (!consumeCommaIncludingWhitespace(args) || args.peek().type() != StringToken)
+ return nullptr;
+ separator = CSSPrimitiveValue::create(args.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
+ }
+
+ RefPtr<CSSPrimitiveValue> listStyle;
+ if (consumeCommaIncludingWhitespace(args)) {
+ CSSValueID id = args.peek().id();
+ if ((id != CSSValueNone && (id < CSSValueDisc || id > CSSValueKatakanaIroha)))
+ return nullptr;
+ listStyle = consumeIdent(args);
+ } else
+ listStyle = CSSValuePool::singleton().createIdentifierValue(CSSValueDecimal);
+
+ if (!args.atEnd())
+ return nullptr;
+
+ // FIXME-NEWPARSER: Should just have a CSSCounterValue.
+ return CSSValuePool::singleton().createValue(Counter::create(identifier.releaseNonNull(), listStyle.releaseNonNull(), separator.releaseNonNull()));
+}
+
+static RefPtr<CSSValue> consumeContent(CSSParserTokenRange& range, CSSParserContext context)
+{
+ if (identMatches<CSSValueNone, CSSValueNormal>(range.peek().id()))
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> values = CSSValueList::createSpaceSeparated();
+
+ do {
+ RefPtr<CSSValue> parsedValue = consumeImage(range, context);
+ if (!parsedValue)
+ parsedValue = consumeIdent<CSSValueOpenQuote, CSSValueCloseQuote, CSSValueNoOpenQuote, CSSValueNoCloseQuote>(range);
+ if (!parsedValue)
+ parsedValue = consumeString(range);
+ if (!parsedValue) {
+ if (range.peek().functionId() == CSSValueAttr)
+ parsedValue = consumeAttr(consumeFunction(range), context);
+ else if (range.peek().functionId() == CSSValueCounter)
+ parsedValue = consumeCounterContent(consumeFunction(range), false);
+ else if (range.peek().functionId() == CSSValueCounters)
+ parsedValue = consumeCounterContent(consumeFunction(range), true);
+ if (!parsedValue)
+ return nullptr;
+ }
+ values->append(parsedValue.releaseNonNull());
+ } while (!range.atEnd());
+
+ return values;
+}
+
+static RefPtr<CSSPrimitiveValue> consumePerspective(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ RefPtr<CSSPrimitiveValue> parsedValue = consumeLength(range, cssParserMode, ValueRangeAll);
+ if (!parsedValue) {
+ // FIXME: Make this quirk only apply to the webkit prefixed version of the property.
+ double perspective;
+ if (!consumeNumberRaw(range, perspective))
+ return nullptr;
+ parsedValue = CSSPrimitiveValue::create(perspective, CSSPrimitiveValue::UnitType::CSS_PX);
+ }
+ if (parsedValue && (parsedValue->isCalculated() || parsedValue->doubleValue() > 0))
+ return parsedValue;
+ return nullptr;
+}
+
+#if ENABLE(CSS_SCROLL_SNAP)
+
+static RefPtr<CSSValueList> consumeScrollSnapAlign(CSSParserTokenRange& range)
+{
+ RefPtr<CSSValueList> alignmentValue = CSSValueList::createSpaceSeparated();
+ if (RefPtr<CSSPrimitiveValue> firstValue = consumeIdent<CSSValueNone, CSSValueStart, CSSValueCenter, CSSValueEnd>(range)) {
+ alignmentValue->append(firstValue.releaseNonNull());
+ if (auto secondValue = consumeIdent<CSSValueNone, CSSValueStart, CSSValueCenter, CSSValueEnd>(range))
+ alignmentValue->append(secondValue.releaseNonNull());
+ }
+ return alignmentValue->length() ? alignmentValue : nullptr;
+}
+
+static RefPtr<CSSValueList> consumeScrollSnapType(CSSParserTokenRange& range)
+{
+ RefPtr<CSSValueList> typeValue = CSSValueList::createSpaceSeparated();
+ RefPtr<CSSPrimitiveValue> secondValue;
+
+ auto firstValue = consumeIdent<CSSValueX, CSSValueY, CSSValueBlock, CSSValueInline, CSSValueBoth>(range);
+ if (firstValue)
+ secondValue = consumeIdent<CSSValueProximity, CSSValueMandatory>(range);
+ else
+ firstValue = consumeIdent<CSSValueNone, CSSValueProximity, CSSValueMandatory>(range);
+
+ if (!firstValue)
+ return nullptr;
+
+ typeValue->append(firstValue.releaseNonNull());
+ if (secondValue)
+ typeValue->append(secondValue.releaseNonNull());
+
+ return typeValue;
+}
+
+#endif
+
+static RefPtr<CSSValue> consumeBorderRadiusCorner(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ RefPtr<CSSPrimitiveValue> parsedValue1 = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative);
+ if (!parsedValue1)
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> parsedValue2 = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative);
+ if (!parsedValue2)
+ parsedValue2 = parsedValue1;
+ return createPrimitiveValuePair(parsedValue1.releaseNonNull(), parsedValue2.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeVerticalAlign(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ RefPtr<CSSPrimitiveValue> parsedValue = consumeIdentRange(range, CSSValueBaseline, CSSValueWebkitBaselineMiddle);
+ if (!parsedValue)
+ parsedValue = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow);
+ return parsedValue;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeShapeRadius(CSSParserTokenRange& args, CSSParserMode cssParserMode)
+{
+ if (identMatches<CSSValueClosestSide, CSSValueFarthestSide>(args.peek().id()))
+ return consumeIdent(args);
+ return consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
+}
+
+static RefPtr<CSSBasicShapeCircle> consumeBasicShapeCircle(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+ // spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes
+ // circle( [<shape-radius>]? [at <position>]? )
+ RefPtr<CSSBasicShapeCircle> shape = CSSBasicShapeCircle::create();
+ if (RefPtr<CSSPrimitiveValue> radius = consumeShapeRadius(args, context.mode))
+ shape->setRadius(radius.releaseNonNull());
+ if (consumeIdent<CSSValueAt>(args)) {
+ RefPtr<CSSPrimitiveValue> centerX;
+ RefPtr<CSSPrimitiveValue> centerY;
+ if (!consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY))
+ return nullptr;
+ shape->setCenterX(centerX.releaseNonNull());
+ shape->setCenterY(centerY.releaseNonNull());
+ }
+ return shape;
+}
+
+static RefPtr<CSSBasicShapeEllipse> consumeBasicShapeEllipse(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+ // spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes
+ // ellipse( [<shape-radius>{2}]? [at <position>]? )
+ RefPtr<CSSBasicShapeEllipse> shape = CSSBasicShapeEllipse::create();
+ if (RefPtr<CSSPrimitiveValue> radiusX = consumeShapeRadius(args, context.mode)) {
+ shape->setRadiusX(radiusX.releaseNonNull());
+ if (RefPtr<CSSPrimitiveValue> radiusY = consumeShapeRadius(args, context.mode))
+ shape->setRadiusY(radiusY.releaseNonNull());
+ }
+ if (consumeIdent<CSSValueAt>(args)) {
+ RefPtr<CSSPrimitiveValue> centerX;
+ RefPtr<CSSPrimitiveValue> centerY;
+ if (!consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY))
+ return nullptr;
+ shape->setCenterX(centerX.releaseNonNull());
+ shape->setCenterY(centerY.releaseNonNull());
+ }
+ return shape;
+}
+
+static RefPtr<CSSBasicShapePolygon> consumeBasicShapePolygon(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+ RefPtr<CSSBasicShapePolygon> shape = CSSBasicShapePolygon::create();
+ if (identMatches<CSSValueEvenodd, CSSValueNonzero>(args.peek().id())) {
+ shape->setWindRule(args.consumeIncludingWhitespace().id() == CSSValueEvenodd ? RULE_EVENODD : RULE_NONZERO);
+ if (!consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ }
+
+ do {
+ RefPtr<CSSPrimitiveValue> xLength = consumeLengthOrPercent(args, context.mode, ValueRangeAll);
+ if (!xLength)
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> yLength = consumeLengthOrPercent(args, context.mode, ValueRangeAll);
+ if (!yLength)
+ return nullptr;
+ shape->appendPoint(xLength.releaseNonNull(), yLength.releaseNonNull());
+ } while (consumeCommaIncludingWhitespace(args));
+ return shape;
+}
+
+static RefPtr<CSSBasicShapePath> consumeBasicShapePath(CSSParserTokenRange& args)
+{
+ WindRule windRule = RULE_NONZERO;
+ if (identMatches<CSSValueEvenodd, CSSValueNonzero>(args.peek().id())) {
+ windRule = args.consumeIncludingWhitespace().id() == CSSValueEvenodd ? RULE_EVENODD : RULE_NONZERO;
+ if (!consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ }
+
+ if (args.peek().type() != StringToken)
+ return nullptr;
+
+ auto byteStream = std::make_unique<SVGPathByteStream>();
+ if (!buildSVGPathByteStreamFromString(args.consumeIncludingWhitespace().value().toString(), *byteStream, UnalteredParsing))
+ return nullptr;
+
+ auto shape = CSSBasicShapePath::create(WTFMove(byteStream));
+ shape->setWindRule(windRule);
+
+ return WTFMove(shape);
+}
+
+static void complete4Sides(RefPtr<CSSPrimitiveValue> side[4])
+{
+ if (side[3])
+ return;
+ if (!side[2]) {
+ if (!side[1])
+ side[1] = side[0];
+ side[2] = side[0];
+ }
+ side[3] = side[1];
+}
+
+static bool consumeRadii(RefPtr<CSSPrimitiveValue> horizontalRadii[4], RefPtr<CSSPrimitiveValue> verticalRadii[4], CSSParserTokenRange& range, CSSParserMode cssParserMode, bool useLegacyParsing)
+{
+ unsigned i = 0;
+ for (; i < 4 && !range.atEnd() && range.peek().type() != DelimiterToken; ++i) {
+ horizontalRadii[i] = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative);
+ if (!horizontalRadii[i])
+ return false;
+ }
+ if (!horizontalRadii[0])
+ return false;
+ if (range.atEnd()) {
+ // Legacy syntax: -webkit-border-radius: l1 l2; is equivalent to border-radius: l1 / l2;
+ if (useLegacyParsing && i == 2) {
+ verticalRadii[0] = horizontalRadii[1];
+ horizontalRadii[1] = nullptr;
+ } else {
+ complete4Sides(horizontalRadii);
+ for (unsigned i = 0; i < 4; ++i)
+ verticalRadii[i] = horizontalRadii[i];
+ return true;
+ }
+ } else {
+ if (!consumeSlashIncludingWhitespace(range))
+ return false;
+ for (i = 0; i < 4 && !range.atEnd(); ++i) {
+ verticalRadii[i] = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative);
+ if (!verticalRadii[i])
+ return false;
+ }
+ if (!verticalRadii[0] || !range.atEnd())
+ return false;
+ }
+ complete4Sides(horizontalRadii);
+ complete4Sides(verticalRadii);
+ return true;
+}
+
+static RefPtr<CSSBasicShapeInset> consumeBasicShapeInset(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+ RefPtr<CSSBasicShapeInset> shape = CSSBasicShapeInset::create();
+ RefPtr<CSSPrimitiveValue> top = consumeLengthOrPercent(args, context.mode, ValueRangeAll);
+ if (!top)
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> right = consumeLengthOrPercent(args, context.mode, ValueRangeAll);
+ RefPtr<CSSPrimitiveValue> bottom;
+ RefPtr<CSSPrimitiveValue> left;
+ if (right) {
+ bottom = consumeLengthOrPercent(args, context.mode, ValueRangeAll);
+ if (bottom)
+ left = consumeLengthOrPercent(args, context.mode, ValueRangeAll);
+ }
+ if (left)
+ shape->updateShapeSize4Values(top.releaseNonNull(), right.releaseNonNull(), bottom.releaseNonNull(), left.releaseNonNull());
+ else if (bottom)
+ shape->updateShapeSize3Values(top.releaseNonNull(), right.releaseNonNull(), bottom.releaseNonNull());
+ else if (right)
+ shape->updateShapeSize2Values(top.releaseNonNull(), right.releaseNonNull());
+ else
+ shape->updateShapeSize1Value(top.releaseNonNull());
+
+ if (consumeIdent<CSSValueRound>(args)) {
+ RefPtr<CSSPrimitiveValue> horizontalRadii[4] = { 0 };
+ RefPtr<CSSPrimitiveValue> verticalRadii[4] = { 0 };
+ if (!consumeRadii(horizontalRadii, verticalRadii, args, context.mode, false))
+ return nullptr;
+ shape->setTopLeftRadius(createPrimitiveValuePair(horizontalRadii[0].releaseNonNull(), verticalRadii[0].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce));
+ shape->setTopRightRadius(createPrimitiveValuePair(horizontalRadii[1].releaseNonNull(), verticalRadii[1].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce));
+ shape->setBottomRightRadius(createPrimitiveValuePair(horizontalRadii[2].releaseNonNull(), verticalRadii[2].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce));
+ shape->setBottomLeftRadius(createPrimitiveValuePair(horizontalRadii[3].releaseNonNull(), verticalRadii[3].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce));
+ }
+ return shape;
+}
+
+static RefPtr<CSSValue> consumeBasicShape(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ RefPtr<CSSValue> result;
+ if (range.peek().type() != FunctionToken)
+ return nullptr;
+ CSSValueID id = range.peek().functionId();
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+
+ // FIXME-NEWPARSER: CSSBasicShape should be a CSSValue, and shapes should not be primitive values.
+ RefPtr<CSSBasicShape> shape;
+ if (id == CSSValueCircle)
+ shape = consumeBasicShapeCircle(args, context);
+ else if (id == CSSValueEllipse)
+ shape = consumeBasicShapeEllipse(args, context);
+ else if (id == CSSValuePolygon)
+ shape = consumeBasicShapePolygon(args, context);
+ else if (id == CSSValueInset)
+ shape = consumeBasicShapeInset(args, context);
+ else if (id == CSSValuePath)
+ shape = consumeBasicShapePath(args);
+ if (!shape)
+ return nullptr;
+ range = rangeCopy;
+
+ if (!args.atEnd())
+ return nullptr;
+
+ return CSSValuePool::singleton().createValue(shape.releaseNonNull());
+}
+
+static RefPtr<CSSValue> consumeBasicShapeOrBox(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ bool shapeFound = false;
+ bool boxFound = false;
+ while (!range.atEnd() && !(shapeFound && boxFound)) {
+ RefPtr<CSSValue> componentValue;
+ if (range.peek().type() == FunctionToken && !shapeFound) {
+ componentValue = consumeBasicShape(range, context);
+ shapeFound = true;
+ } else if (range.peek().type() == IdentToken && !boxFound) {
+ componentValue = consumeIdent<CSSValueContentBox, CSSValuePaddingBox, CSSValueBorderBox, CSSValueMarginBox, CSSValueFill, CSSValueStroke, CSSValueViewBox>(range);
+ boxFound = true;
+ }
+ if (!componentValue)
+ return nullptr;
+ list->append(componentValue.releaseNonNull());
+ }
+
+ if (!range.atEnd() || !list->length())
+ return nullptr;
+
+ return list;
+}
+
+static RefPtr<CSSValue> consumeWebkitClipPath(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ if (RefPtr<CSSPrimitiveValue> url = consumeUrl(range))
+ return url;
+ return consumeBasicShapeOrBox(range, context);
+}
+
+static RefPtr<CSSValue> consumeShapeOutside(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ if (RefPtr<CSSValue> imageValue = consumeImageOrNone(range, context))
+ return imageValue;
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ if (RefPtr<CSSValue> boxValue = consumeIdent<CSSValueContentBox, CSSValuePaddingBox, CSSValueBorderBox, CSSValueMarginBox>(range))
+ list->append(boxValue.releaseNonNull());
+ if (RefPtr<CSSValue> shapeValue = consumeBasicShape(range, context)) {
+ list->append(shapeValue.releaseNonNull());
+ if (list->length() < 2) {
+ if (RefPtr<CSSValue> boxValue = consumeIdent<CSSValueContentBox, CSSValuePaddingBox, CSSValueBorderBox, CSSValueMarginBox>(range))
+ list->append(boxValue.releaseNonNull());
+ }
+ }
+ if (!list->length())
+ return nullptr;
+ return list;
+}
+
+static RefPtr<CSSValue> consumeContentDistributionOverflowPosition(CSSParserTokenRange& range)
+{
+ if (identMatches<CSSValueNormal, CSSValueBaseline, CSSValueLastBaseline>(range.peek().id()))
+ return CSSContentDistributionValue::create(CSSValueInvalid, range.consumeIncludingWhitespace().id(), CSSValueInvalid);
+
+ CSSValueID distribution = CSSValueInvalid;
+ CSSValueID position = CSSValueInvalid;
+ CSSValueID overflow = CSSValueInvalid;
+ do {
+ CSSValueID id = range.peek().id();
+ if (identMatches<CSSValueSpaceBetween, CSSValueSpaceAround, CSSValueSpaceEvenly, CSSValueStretch>(id)) {
+ if (distribution != CSSValueInvalid)
+ return nullptr;
+ distribution = id;
+ } else if (identMatches<CSSValueStart, CSSValueEnd, CSSValueCenter, CSSValueFlexStart, CSSValueFlexEnd, CSSValueLeft, CSSValueRight>(id)) {
+ if (position != CSSValueInvalid)
+ return nullptr;
+ position = id;
+ } else if (identMatches<CSSValueUnsafe, CSSValueSafe>(id)) {
+ if (overflow != CSSValueInvalid)
+ return nullptr;
+ overflow = id;
+ } else {
+ return nullptr;
+ }
+ range.consumeIncludingWhitespace();
+ } while (!range.atEnd());
+
+ // The grammar states that we should have at least <content-distribution> or <content-position>.
+ if (position == CSSValueInvalid && distribution == CSSValueInvalid)
+ return nullptr;
+
+ // The grammar states that <overflow-position> must be associated to <content-position>.
+ if (overflow != CSSValueInvalid && position == CSSValueInvalid)
+ return nullptr;
+
+ return CSSContentDistributionValue::create(distribution, position, overflow);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeBorderImageRepeatKeyword(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueStretch, CSSValueRepeat, CSSValueSpace, CSSValueRound>(range);
+}
+
+static RefPtr<CSSValue> consumeBorderImageRepeat(CSSParserTokenRange& range)
+{
+ RefPtr<CSSPrimitiveValue> horizontal = consumeBorderImageRepeatKeyword(range);
+ if (!horizontal)
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> vertical = consumeBorderImageRepeatKeyword(range);
+ if (!vertical)
+ vertical = horizontal;
+ return createPrimitiveValuePair(horizontal.releaseNonNull(), vertical.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce);
+}
+
+static RefPtr<CSSValue> consumeBorderImageSlice(CSSPropertyID property, CSSParserTokenRange& range)
+{
+ bool fill = consumeIdent<CSSValueFill>(range);
+ RefPtr<CSSPrimitiveValue> slices[4] = { 0 };
+
+ for (size_t index = 0; index < 4; ++index) {
+ RefPtr<CSSPrimitiveValue> value = consumePercent(range, ValueRangeNonNegative);
+ if (!value)
+ value = consumeNumber(range, ValueRangeNonNegative);
+ if (!value)
+ break;
+ slices[index] = value;
+ }
+ if (!slices[0])
+ return nullptr;
+ if (consumeIdent<CSSValueFill>(range)) {
+ if (fill)
+ return nullptr;
+ fill = true;
+ }
+ complete4Sides(slices);
+ // FIXME: For backwards compatibility, -webkit-border-image, -webkit-mask-box-image and -webkit-box-reflect have to do a fill by default.
+ // FIXME: What do we do with -webkit-box-reflect and -webkit-mask-box-image? Probably just have to leave them filling...
+ if (property == CSSPropertyWebkitBorderImage || property == CSSPropertyWebkitMaskBoxImage || property == CSSPropertyWebkitBoxReflect)
+ fill = true;
+
+ // Now build a rect value to hold all four of our primitive values.
+ // FIXME-NEWPARSER: Should just have a CSSQuadValue.
+ auto quad = Quad::create();
+ quad->setTop(slices[0].releaseNonNull());
+ quad->setRight(slices[1].releaseNonNull());
+ quad->setBottom(slices[2].releaseNonNull());
+ quad->setLeft(slices[3].releaseNonNull());
+
+ // Make our new border image value now.
+ return CSSBorderImageSliceValue::create(CSSValuePool::singleton().createValue(WTFMove(quad)), fill);
+}
+
+static RefPtr<CSSValue> consumeBorderImageOutset(CSSParserTokenRange& range)
+{
+ RefPtr<CSSPrimitiveValue> outsets[4] = { 0 };
+
+ RefPtr<CSSPrimitiveValue> value;
+ for (size_t index = 0; index < 4; ++index) {
+ value = consumeNumber(range, ValueRangeNonNegative);
+ if (!value)
+ value = consumeLength(range, HTMLStandardMode, ValueRangeNonNegative);
+ if (!value)
+ break;
+ outsets[index] = value;
+ }
+ if (!outsets[0])
+ return nullptr;
+ complete4Sides(outsets);
+
+ // FIXME-NEWPARSER: Should just have a CSSQuadValue.
+ auto quad = Quad::create();
+ quad->setTop(outsets[0].releaseNonNull());
+ quad->setRight(outsets[1].releaseNonNull());
+ quad->setBottom(outsets[2].releaseNonNull());
+ quad->setLeft(outsets[3].releaseNonNull());
+
+ return CSSValuePool::singleton().createValue(WTFMove(quad));
+}
+
+static RefPtr<CSSValue> consumeBorderImageWidth(CSSParserTokenRange& range)
+{
+ RefPtr<CSSPrimitiveValue> widths[4];
+
+ RefPtr<CSSPrimitiveValue> value;
+ for (size_t index = 0; index < 4; ++index) {
+ value = consumeNumber(range, ValueRangeNonNegative);
+ if (!value)
+ value = consumeLengthOrPercent(range, HTMLStandardMode, ValueRangeNonNegative, UnitlessQuirk::Forbid);
+ if (!value)
+ value = consumeIdent<CSSValueAuto>(range);
+ if (!value)
+ break;
+ widths[index] = value;
+ }
+ if (!widths[0])
+ return nullptr;
+ complete4Sides(widths);
+
+ // FIXME-NEWPARSER: Should just have a CSSQuadValue.
+ auto quad = Quad::create();
+ quad->setTop(widths[0].releaseNonNull());
+ quad->setRight(widths[1].releaseNonNull());
+ quad->setBottom(widths[2].releaseNonNull());
+ quad->setLeft(widths[3].releaseNonNull());
+
+ return CSSValuePool::singleton().createValue(WTFMove(quad));
+}
+
+static bool consumeBorderImageComponents(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context, RefPtr<CSSValue>& source,
+ RefPtr<CSSValue>& slice, RefPtr<CSSValue>& width, RefPtr<CSSValue>& outset, RefPtr<CSSValue>& repeat)
+{
+ do {
+ if (!source) {
+ source = consumeImageOrNone(range, context);
+ if (source)
+ continue;
+ }
+ if (!repeat) {
+ repeat = consumeBorderImageRepeat(range);
+ if (repeat)
+ continue;
+ }
+ if (!slice) {
+ slice = consumeBorderImageSlice(property, range);
+ if (slice) {
+ ASSERT(!width && !outset);
+ if (consumeSlashIncludingWhitespace(range)) {
+ width = consumeBorderImageWidth(range);
+ if (consumeSlashIncludingWhitespace(range)) {
+ outset = consumeBorderImageOutset(range);
+ if (!outset)
+ return false;
+ } else if (!width) {
+ return false;
+ }
+ }
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } while (!range.atEnd());
+ return true;
+}
+
+static RefPtr<CSSValue> consumeWebkitBorderImage(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ RefPtr<CSSValue> source;
+ RefPtr<CSSValue> slice;
+ RefPtr<CSSValue> width;
+ RefPtr<CSSValue> outset;
+ RefPtr<CSSValue> repeat;
+ if (consumeBorderImageComponents(property, range, context, source, slice, width, outset, repeat))
+ return createBorderImageValue(WTFMove(source), WTFMove(slice), WTFMove(width), WTFMove(outset), WTFMove(repeat));
+ return nullptr;
+}
+
+static RefPtr<CSSValue> consumeReflect(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSPrimitiveValue> direction = consumeIdent<CSSValueAbove, CSSValueBelow, CSSValueLeft, CSSValueRight>(range);
+ if (!direction)
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> offset;
+ if (range.atEnd())
+ offset = CSSValuePool::singleton().createValue(0, CSSPrimitiveValue::UnitType::CSS_PX);
+ else {
+ offset = consumeLengthOrPercent(range, context.mode, ValueRangeAll, UnitlessQuirk::Forbid);
+ if (!offset)
+ return nullptr;
+ }
+
+ RefPtr<CSSValue> mask;
+ if (!range.atEnd()) {
+ mask = consumeWebkitBorderImage(CSSPropertyWebkitBoxReflect, range, context);
+ if (!mask)
+ return nullptr;
+ }
+ return CSSReflectValue::create(direction.releaseNonNull(), offset.releaseNonNull(), WTFMove(mask));
+}
+
+#if ENABLE(CSS_IMAGE_ORIENTATION)
+static RefPtr<CSSValue> consumeImageOrientation(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless = UnitlessQuirk::Forbid)
+{
+ if (range.peek().type() != NumberToken) {
+ RefPtr<CSSPrimitiveValue> angle = consumeAngle(range, cssParserMode, unitless);
+ if (angle && angle->doubleValue() == 0)
+ return angle;
+ }
+ return nullptr;
+}
+#endif
+
+static RefPtr<CSSPrimitiveValue> consumeBackgroundBlendMode(CSSParserTokenRange& range)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueNormal || id == CSSValueOverlay || (id >= CSSValueMultiply && id <= CSSValueLuminosity))
+ return consumeIdent(range);
+ return nullptr;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeBackgroundAttachment(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueScroll, CSSValueFixed, CSSValueLocal>(range);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeBackgroundBox(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueBorderBox, CSSValuePaddingBox, CSSValueContentBox, CSSValueWebkitText>(range);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeBackgroundComposite(CSSParserTokenRange& range)
+{
+ return consumeIdentRange(range, CSSValueClear, CSSValuePlusLighter);
+}
+
+static RefPtr<CSSPrimitiveValue> consumeWebkitMaskSourceType(CSSParserTokenRange& range)
+{
+ return consumeIdent<CSSValueAuto, CSSValueAlpha, CSSValueLuminance>(range);
+}
+
+static RefPtr<CSSPrimitiveValue> consumePrefixedBackgroundBox(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& /*context*/)
+{
+ // The values 'border', 'padding' and 'content' are deprecated and do not apply to the version of the property that has the -webkit- prefix removed.
+ if (RefPtr<CSSPrimitiveValue> value = consumeIdentRange(range, CSSValueBorder, CSSValuePaddingBox))
+ return value;
+ if (range.peek().id() == CSSValueWebkitText || ((property == CSSPropertyWebkitBackgroundClip || property == CSSPropertyWebkitMaskClip) && range.peek().id() == CSSValueText))
+ return consumeIdent(range);
+ return nullptr;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeBackgroundSize(CSSPropertyID property, CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (identMatches<CSSValueContain, CSSValueCover>(range.peek().id()))
+ return consumeIdent(range);
+
+ // FIXME: We're allowing the unitless quirk on this property because our
+ // tests assume that. Other browser engines don't allow it though.
+ RefPtr<CSSPrimitiveValue> horizontal = consumeIdent<CSSValueAuto>(range);
+ if (!horizontal)
+ horizontal = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow);
+
+ RefPtr<CSSPrimitiveValue> vertical;
+ if (!range.atEnd()) {
+ if (range.peek().id() == CSSValueAuto) // `auto' is the default
+ range.consumeIncludingWhitespace();
+ else
+ vertical = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow);
+ } else if (!vertical && property == CSSPropertyWebkitBackgroundSize) {
+ // Legacy syntax: "-webkit-background-size: 10px" is equivalent to "background-size: 10px 10px".
+ vertical = horizontal;
+ }
+ if (!vertical)
+ return horizontal;
+ return createPrimitiveValuePair(horizontal.releaseNonNull(), vertical.releaseNonNull(), property == CSSPropertyWebkitBackgroundSize ? Pair::IdenticalValueEncoding::Coalesce : Pair::IdenticalValueEncoding::DoNotCoalesce);
+}
+
+static RefPtr<CSSValueList> consumeGridAutoFlow(CSSParserTokenRange& range)
+{
+ RefPtr<CSSPrimitiveValue> rowOrColumnValue = consumeIdent<CSSValueRow, CSSValueColumn>(range);
+ RefPtr<CSSPrimitiveValue> denseAlgorithm = consumeIdent<CSSValueDense>(range);
+ if (!rowOrColumnValue) {
+ rowOrColumnValue = consumeIdent<CSSValueRow, CSSValueColumn>(range);
+ if (!rowOrColumnValue && !denseAlgorithm)
+ return nullptr;
+ }
+ RefPtr<CSSValueList> parsedValues = CSSValueList::createSpaceSeparated();
+ if (rowOrColumnValue)
+ parsedValues->append(rowOrColumnValue.releaseNonNull());
+ if (denseAlgorithm)
+ parsedValues->append(denseAlgorithm.releaseNonNull());
+ return parsedValues;
+}
+
+static RefPtr<CSSValue> consumeBackgroundComponent(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ switch (property) {
+ case CSSPropertyBackgroundClip:
+ return consumeBackgroundBox(range);
+ case CSSPropertyBackgroundBlendMode:
+ return consumeBackgroundBlendMode(range);
+ case CSSPropertyBackgroundAttachment:
+ return consumeBackgroundAttachment(range);
+ case CSSPropertyBackgroundOrigin:
+ return consumeBackgroundBox(range);
+ case CSSPropertyWebkitMaskComposite:
+ case CSSPropertyWebkitBackgroundComposite:
+ return consumeBackgroundComposite(range);
+ case CSSPropertyWebkitBackgroundClip:
+ case CSSPropertyWebkitBackgroundOrigin:
+ case CSSPropertyWebkitMaskClip:
+ case CSSPropertyWebkitMaskOrigin:
+ return consumePrefixedBackgroundBox(property, range, context);
+ case CSSPropertyBackgroundImage:
+ case CSSPropertyWebkitMaskImage:
+ return consumeImageOrNone(range, context);
+ case CSSPropertyWebkitMaskSourceType:
+ return consumeWebkitMaskSourceType(range);
+ case CSSPropertyBackgroundPositionX:
+ case CSSPropertyWebkitMaskPositionX:
+ return consumePositionX(range, context.mode);
+ case CSSPropertyBackgroundPositionY:
+ case CSSPropertyWebkitMaskPositionY:
+ return consumePositionY(range, context.mode);
+ case CSSPropertyBackgroundSize:
+ case CSSPropertyWebkitBackgroundSize:
+ case CSSPropertyWebkitMaskSize:
+ return consumeBackgroundSize(property, range, context.mode);
+ case CSSPropertyBackgroundColor:
+ return consumeColor(range, context.mode);
+ default:
+ break;
+ };
+ return nullptr;
+}
+
+static void addBackgroundValue(RefPtr<CSSValue>& list, Ref<CSSValue>&& value)
+{
+ if (list) {
+ if (!list->isBaseValueList()) {
+ RefPtr<CSSValue> firstValue = list;
+ list = CSSValueList::createCommaSeparated();
+ downcast<CSSValueList>(*list).append(firstValue.releaseNonNull());
+ }
+ downcast<CSSValueList>(*list).append(WTFMove(value));
+ } else {
+ // To conserve memory we don't actually wrap a single value in a list.
+ list = WTFMove(value);
+ }
+}
+
+static RefPtr<CSSValue> consumeCommaSeparatedBackgroundComponent(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ RefPtr<CSSValue> result;
+ do {
+ RefPtr<CSSValue> value = consumeBackgroundComponent(property, range, context);
+ if (!value)
+ return nullptr;
+ addBackgroundValue(result, value.releaseNonNull());
+ } while (consumeCommaIncludingWhitespace(range));
+ return result;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeSelfPositionKeyword(CSSParserTokenRange& range)
+{
+ CSSValueID id = range.peek().id();
+ if (id == CSSValueStart || id == CSSValueEnd || id == CSSValueCenter
+ || id == CSSValueSelfStart || id == CSSValueSelfEnd || id == CSSValueFlexStart
+ || id == CSSValueFlexEnd || id == CSSValueLeft || id == CSSValueRight)
+ return consumeIdent(range);
+ return nullptr;
+}
+
+static RefPtr<CSSValue> consumeSelfPositionOverflowPosition(CSSParserTokenRange& range)
+{
+ if (identMatches<CSSValueAuto, CSSValueNormal, CSSValueStretch, CSSValueBaseline, CSSValueLastBaseline>(range.peek().id()))
+ return consumeIdent(range);
+
+ RefPtr<CSSPrimitiveValue> overflowPosition = consumeIdent<CSSValueUnsafe, CSSValueSafe>(range);
+ RefPtr<CSSPrimitiveValue> selfPosition = consumeSelfPositionKeyword(range);
+ if (!selfPosition)
+ return nullptr;
+ if (!overflowPosition)
+ overflowPosition = consumeIdent<CSSValueUnsafe, CSSValueSafe>(range);
+ if (overflowPosition)
+ return createPrimitiveValuePair(selfPosition.releaseNonNull(), overflowPosition.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce);
+ return selfPosition;
+}
+
+static RefPtr<CSSValue> consumeAlignItems(CSSParserTokenRange& range)
+{
+ // align-items property does not allow the 'auto' value.
+ if (identMatches<CSSValueAuto>(range.peek().id()))
+ return nullptr;
+ return consumeSelfPositionOverflowPosition(range);
+}
+
+static RefPtr<CSSValue> consumeJustifyItems(CSSParserTokenRange& range)
+{
+ CSSParserTokenRange rangeCopy = range;
+ RefPtr<CSSPrimitiveValue> legacy = consumeIdent<CSSValueLegacy>(rangeCopy);
+ RefPtr<CSSPrimitiveValue> positionKeyword = consumeIdent<CSSValueCenter, CSSValueLeft, CSSValueRight>(rangeCopy);
+ if (!legacy)
+ legacy = consumeIdent<CSSValueLegacy>(rangeCopy);
+ if (legacy && positionKeyword) {
+ range = rangeCopy;
+ return createPrimitiveValuePair(legacy.releaseNonNull(), positionKeyword.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce);
+ }
+ return consumeSelfPositionOverflowPosition(range);
+}
+
+static RefPtr<CSSValue> consumeFitContent(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+ RefPtr<CSSPrimitiveValue> length = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative, UnitlessQuirk::Allow);
+ if (!length || !args.atEnd())
+ return nullptr;
+ range = rangeCopy;
+ RefPtr<CSSFunctionValue> result = CSSFunctionValue::create(CSSValueFitContent);
+ result->append(length.releaseNonNull());
+ return result;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeCustomIdentForGridLine(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto || range.peek().id() == CSSValueSpan)
+ return nullptr;
+ return consumeCustomIdent(range);
+}
+
+static RefPtr<CSSValue> consumeGridLine(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueAuto)
+ return consumeIdent(range);
+
+ RefPtr<CSSPrimitiveValue> spanValue;
+ RefPtr<CSSPrimitiveValue> gridLineName;
+ RefPtr<CSSPrimitiveValue> numericValue = consumeInteger(range);
+ if (numericValue) {
+ gridLineName = consumeCustomIdentForGridLine(range);
+ spanValue = consumeIdent<CSSValueSpan>(range);
+ } else {
+ spanValue = consumeIdent<CSSValueSpan>(range);
+ if (spanValue) {
+ numericValue = consumeInteger(range);
+ gridLineName = consumeCustomIdentForGridLine(range);
+ if (!numericValue)
+ numericValue = consumeInteger(range);
+ } else {
+ gridLineName = consumeCustomIdentForGridLine(range);
+ if (gridLineName) {
+ numericValue = consumeInteger(range);
+ spanValue = consumeIdent<CSSValueSpan>(range);
+ if (!spanValue && !numericValue)
+ return gridLineName;
+ } else {
+ return nullptr;
+ }
+ }
+ }
+
+ if (spanValue && !numericValue && !gridLineName)
+ return nullptr; // "span" keyword alone is invalid.
+ if (spanValue && numericValue && numericValue->intValue() < 0)
+ return nullptr; // Negative numbers are not allowed for span.
+ if (numericValue && numericValue->intValue() == 0)
+ return nullptr; // An <integer> value of zero makes the declaration invalid.
+
+ RefPtr<CSSValueList> values = CSSValueList::createSpaceSeparated();
+ if (spanValue)
+ values->append(spanValue.releaseNonNull());
+ if (numericValue)
+ values->append(numericValue.releaseNonNull());
+ if (gridLineName)
+ values->append(gridLineName.releaseNonNull());
+ ASSERT(values->length());
+ return values;
+}
+
+static bool isGridTrackFixedSized(const CSSPrimitiveValue& primitiveValue)
+{
+ CSSValueID valueID = primitiveValue.valueID();
+ if (valueID == CSSValueWebkitMinContent || valueID == CSSValueWebkitMaxContent || valueID == CSSValueAuto || primitiveValue.isFlex())
+ return false;
+
+ return true;
+}
+
+static bool isGridTrackFixedSized(const CSSValue& value)
+{
+ if (value.isPrimitiveValue())
+ return isGridTrackFixedSized(downcast<CSSPrimitiveValue>(value));
+
+ ASSERT(value.isFunctionValue());
+ auto& function = downcast<CSSFunctionValue>(value);
+ if (function.name() == CSSValueFitContent || function.length() < 2)
+ return false;
+
+ const CSSValue* minPrimitiveValue = downcast<CSSPrimitiveValue>(function.item(0));
+ const CSSValue* maxPrimitiveValue = downcast<CSSPrimitiveValue>(function.item(1));
+ return isGridTrackFixedSized(*minPrimitiveValue) || isGridTrackFixedSized(*maxPrimitiveValue);
+}
+
+static Vector<String> parseGridTemplateAreasColumnNames(const String& gridRowNames)
+{
+ ASSERT(!gridRowNames.isEmpty());
+ Vector<String> columnNames;
+ // Using StringImpl to avoid checks and indirection in every call to String::operator[].
+ StringImpl& text = *gridRowNames.impl();
+
+ StringBuilder areaName;
+ for (unsigned i = 0; i < text.length(); ++i) {
+ if (isCSSSpace(text[i])) {
+ if (!areaName.isEmpty()) {
+ columnNames.append(areaName.toString());
+ areaName.clear();
+ }
+ continue;
+ }
+ if (text[i] == '.') {
+ if (areaName == ".")
+ continue;
+ if (!areaName.isEmpty()) {
+ columnNames.append(areaName.toString());
+ areaName.clear();
+ }
+ } else {
+ if (!isNameCodePoint(text[i]))
+ return Vector<String>();
+ if (areaName == ".") {
+ columnNames.append(areaName.toString());
+ areaName.clear();
+ }
+ }
+
+ areaName.append(text[i]);
+ }
+
+ if (!areaName.isEmpty())
+ columnNames.append(areaName.toString());
+
+ return columnNames;
+}
+
+static bool parseGridTemplateAreasRow(const String& gridRowNames, NamedGridAreaMap& gridAreaMap, const size_t rowCount, size_t& columnCount)
+{
+ if (gridRowNames.isEmpty() || gridRowNames.containsOnlyWhitespace())
+ return false;
+
+ Vector<String> columnNames = parseGridTemplateAreasColumnNames(gridRowNames);
+ if (rowCount == 0) {
+ columnCount = columnNames.size();
+ if (columnCount == 0)
+ return false;
+ } else if (columnCount != columnNames.size()) {
+ // The declaration is invalid if all the rows don't have the number of columns.
+ return false;
+ }
+
+ for (size_t currentColumn = 0; currentColumn < columnCount; ++currentColumn) {
+ const String& gridAreaName = columnNames[currentColumn];
+
+ // Unamed areas are always valid (we consider them to be 1x1).
+ if (gridAreaName == ".")
+ continue;
+
+ size_t lookAheadColumn = currentColumn + 1;
+ while (lookAheadColumn < columnCount && columnNames[lookAheadColumn] == gridAreaName)
+ lookAheadColumn++;
+
+ NamedGridAreaMap::iterator gridAreaIt = gridAreaMap.find(gridAreaName);
+ if (gridAreaIt == gridAreaMap.end()) {
+ gridAreaMap.add(gridAreaName, GridArea(GridSpan::translatedDefiniteGridSpan(rowCount, rowCount + 1), GridSpan::translatedDefiniteGridSpan(currentColumn, lookAheadColumn)));
+ } else {
+ GridArea& gridArea = gridAreaIt->value;
+
+ // The following checks test that the grid area is a single filled-in rectangle.
+ // 1. The new row is adjacent to the previously parsed row.
+ if (rowCount != gridArea.rows.endLine())
+ return false;
+
+ // 2. The new area starts at the same position as the previously parsed area.
+ if (currentColumn != gridArea.columns.startLine())
+ return false;
+
+ // 3. The new area ends at the same position as the previously parsed area.
+ if (lookAheadColumn != gridArea.columns.endLine())
+ return false;
+
+ gridArea.rows = GridSpan::translatedDefiniteGridSpan(gridArea.rows.startLine(), gridArea.rows.endLine() + 1);
+ }
+ currentColumn = lookAheadColumn - 1;
+ }
+
+ return true;
+}
+
+static RefPtr<CSSPrimitiveValue> consumeGridBreadth(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ const CSSParserToken& token = range.peek();
+ if (identMatches<CSSValueWebkitMinContent, CSSValueWebkitMaxContent, CSSValueAuto>(token.id()))
+ return consumeIdent(range);
+ if (token.type() == DimensionToken && token.unitType() == CSSPrimitiveValue::UnitType::CSS_FR) {
+ if (range.peek().numericValue() < 0)
+ return nullptr;
+ return CSSPrimitiveValue::create(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_FR);
+ }
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumeGridTrackSize(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ const CSSParserToken& token = range.peek();
+ if (identMatches<CSSValueAuto>(token.id()))
+ return consumeIdent(range);
+
+ if (token.functionId() == CSSValueMinmax) {
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+ RefPtr<CSSPrimitiveValue> minTrackBreadth = consumeGridBreadth(args, cssParserMode);
+ if (!minTrackBreadth || minTrackBreadth->isFlex() || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> maxTrackBreadth = consumeGridBreadth(args, cssParserMode);
+ if (!maxTrackBreadth || !args.atEnd())
+ return nullptr;
+ range = rangeCopy;
+ RefPtr<CSSFunctionValue> result = CSSFunctionValue::create(CSSValueMinmax);
+ result->append(minTrackBreadth.releaseNonNull());
+ result->append(maxTrackBreadth.releaseNonNull());
+ return result;
+ }
+
+ if (token.functionId() == CSSValueFitContent)
+ return consumeFitContent(range, cssParserMode);
+
+ return consumeGridBreadth(range, cssParserMode);
+}
+
+// Appends to the passed in CSSGridLineNamesValue if any, otherwise creates a new one.
+static RefPtr<CSSGridLineNamesValue> consumeGridLineNames(CSSParserTokenRange& range, CSSGridLineNamesValue* lineNames = nullptr)
+{
+ CSSParserTokenRange rangeCopy = range;
+ if (rangeCopy.consumeIncludingWhitespace().type() != LeftBracketToken)
+ return nullptr;
+
+ RefPtr<CSSGridLineNamesValue> result = lineNames;
+ if (!result)
+ result = CSSGridLineNamesValue::create();
+ while (RefPtr<CSSPrimitiveValue> lineName = consumeCustomIdentForGridLine(rangeCopy))
+ result->append(lineName.releaseNonNull());
+ if (rangeCopy.consumeIncludingWhitespace().type() != RightBracketToken)
+ return nullptr;
+ range = rangeCopy;
+ return result;
+}
+
+static bool consumeGridTrackRepeatFunction(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSValueList& list, bool& isAutoRepeat, bool& allTracksAreFixedSized)
+{
+ CSSParserTokenRange args = consumeFunction(range);
+ // The number of repetitions for <auto-repeat> is not important at parsing level
+ // because it will be computed later, let's set it to 1.
+ size_t repetitions = 1;
+ isAutoRepeat = identMatches<CSSValueAutoFill, CSSValueAutoFit>(args.peek().id());
+ RefPtr<CSSValueList> repeatedValues;
+ if (isAutoRepeat)
+ repeatedValues = CSSGridAutoRepeatValue::create(args.consumeIncludingWhitespace().id());
+ else {
+ // FIXME: a consumeIntegerRaw would be more efficient here.
+ RefPtr<CSSPrimitiveValue> repetition = consumePositiveInteger(args);
+ if (!repetition)
+ return false;
+ repetitions = clampTo<size_t>(repetition->doubleValue(), 0, kGridMaxTracks);
+ repeatedValues = CSSValueList::createSpaceSeparated();
+ }
+ if (!consumeCommaIncludingWhitespace(args))
+ return false;
+ RefPtr<CSSGridLineNamesValue> lineNames = consumeGridLineNames(args);
+ if (lineNames)
+ repeatedValues->append(lineNames.releaseNonNull());
+
+ size_t numberOfTracks = 0;
+ while (!args.atEnd()) {
+ RefPtr<CSSValue> trackSize = consumeGridTrackSize(args, cssParserMode);
+ if (!trackSize)
+ return false;
+ if (allTracksAreFixedSized)
+ allTracksAreFixedSized = isGridTrackFixedSized(*trackSize);
+ repeatedValues->append(trackSize.releaseNonNull());
+ ++numberOfTracks;
+ lineNames = consumeGridLineNames(args);
+ if (lineNames)
+ repeatedValues->append(lineNames.releaseNonNull());
+ }
+ // We should have found at least one <track-size> or else it is not a valid <track-list>.
+ if (!numberOfTracks)
+ return false;
+
+ if (isAutoRepeat)
+ list.append(repeatedValues.releaseNonNull());
+ else {
+ // We clamp the repetitions to a multiple of the repeat() track list's size, while staying below the max grid size.
+ repetitions = std::min(repetitions, kGridMaxTracks / numberOfTracks);
+ for (size_t i = 0; i < repetitions; ++i) {
+ for (size_t j = 0; j < repeatedValues->length(); ++j)
+ list.append(*repeatedValues->itemWithoutBoundsCheck(j));
+ }
+ }
+ return true;
+}
+
+enum TrackListType { GridTemplate, GridTemplateNoRepeat, GridAuto };
+
+static RefPtr<CSSValue> consumeGridTrackList(CSSParserTokenRange& range, CSSParserMode cssParserMode, TrackListType trackListType)
+{
+ bool allowGridLineNames = trackListType != GridAuto;
+ RefPtr<CSSValueList> values = CSSValueList::createSpaceSeparated();
+ RefPtr<CSSGridLineNamesValue> lineNames = consumeGridLineNames(range);
+ if (lineNames) {
+ if (!allowGridLineNames)
+ return nullptr;
+ values->append(lineNames.releaseNonNull());
+ }
+
+ bool allowRepeat = trackListType == GridTemplate;
+ bool seenAutoRepeat = false;
+ bool allTracksAreFixedSized = true;
+ do {
+ bool isAutoRepeat;
+ if (range.peek().functionId() == CSSValueRepeat) {
+ if (!allowRepeat)
+ return nullptr;
+ if (!consumeGridTrackRepeatFunction(range, cssParserMode, *values, isAutoRepeat, allTracksAreFixedSized))
+ return nullptr;
+ if (isAutoRepeat && seenAutoRepeat)
+ return nullptr;
+ seenAutoRepeat = seenAutoRepeat || isAutoRepeat;
+ } else if (RefPtr<CSSValue> value = consumeGridTrackSize(range, cssParserMode)) {
+ if (allTracksAreFixedSized)
+ allTracksAreFixedSized = isGridTrackFixedSized(*value);
+ values->append(value.releaseNonNull());
+ } else {
+ return nullptr;
+ }
+ if (seenAutoRepeat && !allTracksAreFixedSized)
+ return nullptr;
+ lineNames = consumeGridLineNames(range);
+ if (lineNames) {
+ if (!allowGridLineNames)
+ return nullptr;
+ values->append(lineNames.releaseNonNull());
+ }
+ } while (!range.atEnd() && range.peek().type() != DelimiterToken);
+ return values;
+}
+
+static RefPtr<CSSValue> consumeGridTemplatesRowsOrColumns(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ return consumeGridTrackList(range, cssParserMode, GridTemplate);
+}
+
+static RefPtr<CSSValue> consumeGridTemplateAreas(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ NamedGridAreaMap gridAreaMap;
+ size_t rowCount = 0;
+ size_t columnCount = 0;
+
+ while (range.peek().type() == StringToken) {
+ if (!parseGridTemplateAreasRow(range.consumeIncludingWhitespace().value().toString(), gridAreaMap, rowCount, columnCount))
+ return nullptr;
+ ++rowCount;
+ }
+
+ if (rowCount == 0)
+ return nullptr;
+ ASSERT(columnCount);
+ return CSSGridTemplateAreasValue::create(gridAreaMap, rowCount, columnCount);
+}
+
+#if ENABLE(CSS_REGIONS)
+static RefPtr<CSSValue> consumeFlowProperty(CSSParserTokenRange& range)
+{
+ RefPtr<CSSValue> result;
+ if (range.peek().id() == CSSValueNone)
+ result = consumeIdent(range);
+ else
+ result = consumeCustomIdent(range);
+ return result;
+}
+#endif
+
+static RefPtr<CSSValue> consumeLineBoxContain(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ LineBoxContain lineBoxContain = LineBoxContainNone;
+
+ while (range.peek().type() == IdentToken) {
+ auto id = range.peek().id();
+ if (id == CSSValueBlock) {
+ if (lineBoxContain & LineBoxContainBlock)
+ return nullptr;
+ lineBoxContain |= LineBoxContainBlock;
+ } else if (id == CSSValueInline) {
+ if (lineBoxContain & LineBoxContainInline)
+ return nullptr;
+ lineBoxContain |= LineBoxContainInline;
+ } else if (id == CSSValueFont) {
+ if (lineBoxContain & LineBoxContainFont)
+ return nullptr;
+ lineBoxContain |= LineBoxContainFont;
+ } else if (id == CSSValueGlyphs) {
+ if (lineBoxContain & LineBoxContainGlyphs)
+ return nullptr;
+ lineBoxContain |= LineBoxContainGlyphs;
+ } else if (id == CSSValueReplaced) {
+ if (lineBoxContain & LineBoxContainReplaced)
+ return nullptr;
+ lineBoxContain |= LineBoxContainReplaced;
+ } else if (id == CSSValueInlineBox) {
+ if (lineBoxContain & LineBoxContainInlineBox)
+ return nullptr;
+ lineBoxContain |= LineBoxContainInlineBox;
+ } else if (id == CSSValueInitialLetter) {
+ if (lineBoxContain & LineBoxContainInitialLetter)
+ return nullptr;
+ lineBoxContain |= LineBoxContainInitialLetter;
+ } else
+ return nullptr;
+ range.consumeIncludingWhitespace();
+ }
+
+ if (!lineBoxContain)
+ return nullptr;
+
+ return CSSLineBoxContainValue::create(lineBoxContain);
+}
+
+static RefPtr<CSSValue> consumeLineGrid(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ return consumeCustomIdent(range);
+}
+
+static RefPtr<CSSValue> consumeInitialLetter(CSSParserTokenRange& range)
+{
+ RefPtr<CSSValue> ident = consumeIdent<CSSValueNormal>(range);
+ if (ident)
+ return ident;
+
+ RefPtr<CSSPrimitiveValue> height = consumeNumber(range, ValueRangeNonNegative);
+ if (!height)
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> position;
+ if (!range.atEnd()) {
+ position = consumeNumber(range, ValueRangeNonNegative);
+ if (!position || !range.atEnd())
+ return nullptr;
+ } else
+ position = height.copyRef();
+
+ return createPrimitiveValuePair(position.releaseNonNull(), WTFMove(height));
+}
+
+static RefPtr<CSSValue> consumeHangingPunctuation(CSSParserTokenRange& range)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ std::bitset<numCSSValueKeywords> seenValues;
+
+ bool seenForceEnd = false;
+ bool seenAllowEnd = false;
+ bool seenFirst = false;
+ bool seenLast = false;
+
+ while (!range.atEnd()) {
+ CSSValueID valueID = range.peek().id();
+ if ((valueID == CSSValueFirst && seenFirst)
+ || (valueID == CSSValueLast && seenLast)
+ || (valueID == CSSValueAllowEnd && (seenAllowEnd || seenForceEnd))
+ || (valueID == CSSValueForceEnd && (seenAllowEnd || seenForceEnd)))
+ return nullptr;
+ RefPtr<CSSValue> ident = consumeIdent<CSSValueAllowEnd, CSSValueForceEnd, CSSValueFirst, CSSValueLast>(range);
+ if (!ident)
+ return nullptr;
+ switch (valueID) {
+ case CSSValueAllowEnd:
+ seenAllowEnd = true;
+ break;
+ case CSSValueForceEnd:
+ seenForceEnd = true;
+ break;
+ case CSSValueFirst:
+ seenFirst = true;
+ break;
+ case CSSValueLast:
+ seenLast = true;
+ break;
+ default:
+ break;
+ }
+ list->append(ident.releaseNonNull());
+ }
+
+ return list->length() ? list : nullptr;
+}
+
+static RefPtr<CSSValue> consumeWebkitMarqueeIncrement(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().type() == IdentToken)
+ return consumeIdent<CSSValueSmall, CSSValueMedium, CSSValueLarge>(range);
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumeWebkitMarqueeRepetition(CSSParserTokenRange& range)
+{
+ if (range.peek().type() == IdentToken)
+ return consumeIdent<CSSValueInfinite>(range);
+ return consumeNumber(range, ValueRangeNonNegative);
+}
+
+static RefPtr<CSSValue> consumeWebkitMarqueeSpeed(CSSParserTokenRange& range, CSSParserMode cssParserMode)
+{
+ if (range.peek().type() == IdentToken)
+ return consumeIdent<CSSValueSlow, CSSValueNormal, CSSValueFast>(range);
+ return consumeTime(range, cssParserMode, ValueRangeNonNegative, UnitlessQuirk::Allow);
+}
+
+static RefPtr<CSSValue> consumeAlt(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ if (range.peek().type() == StringToken)
+ return consumeString(range);
+
+ if (range.peek().functionId() != CSSValueAttr)
+ return nullptr;
+
+ return consumeAttr(consumeFunction(range), context);
+}
+
+static RefPtr<CSSValue> consumeWebkitAspectRatio(CSSParserTokenRange& range)
+{
+ if (range.peek().type() == IdentToken)
+ return consumeIdent<CSSValueAuto, CSSValueFromDimensions, CSSValueFromIntrinsic>(range);
+
+ RefPtr<CSSPrimitiveValue> leftValue = consumeNumber(range, ValueRangeNonNegative);
+ if (!leftValue || !leftValue->floatValue() || range.atEnd() || !consumeSlashIncludingWhitespace(range))
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> rightValue = consumeNumber(range, ValueRangeNonNegative);
+ if (!rightValue || !rightValue->floatValue())
+ return nullptr;
+
+ return CSSAspectRatioValue::create(leftValue->floatValue(), rightValue->floatValue());
+}
+
+static RefPtr<CSSValue> consumeTextEmphasisPosition(CSSParserTokenRange& range)
+{
+ bool foundOverOrUnder = false;
+ CSSValueID overUnderValueID = CSSValueOver;
+ bool foundLeftOrRight = false;
+ CSSValueID leftRightValueID = CSSValueRight;
+ while (!range.atEnd()) {
+ switch (range.peek().id()) {
+ case CSSValueOver:
+ if (foundOverOrUnder)
+ return nullptr;
+ foundOverOrUnder = true;
+ overUnderValueID = CSSValueOver;
+ break;
+ case CSSValueUnder:
+ if (foundOverOrUnder)
+ return nullptr;
+ foundOverOrUnder = true;
+ overUnderValueID = CSSValueUnder;
+ break;
+ case CSSValueLeft:
+ if (foundLeftOrRight)
+ return nullptr;
+ foundLeftOrRight = true;
+ leftRightValueID = CSSValueLeft;
+ break;
+ case CSSValueRight:
+ if (foundLeftOrRight)
+ return nullptr;
+ foundLeftOrRight = true;
+ leftRightValueID = CSSValueRight;
+ break;
+ default:
+ return nullptr;
+ }
+
+ range.consumeIncludingWhitespace();
+ }
+ if (!foundOverOrUnder)
+ return nullptr;
+ RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated();
+ list->append(CSSValuePool::singleton().createIdentifierValue(overUnderValueID));
+ if (foundLeftOrRight)
+ list->append(CSSValuePool::singleton().createIdentifierValue(leftRightValueID));
+ return list;
+}
+
+#if ENABLE(DASHBOARD_SUPPORT)
+
+static RefPtr<CSSValue> consumeWebkitDashboardRegion(CSSParserTokenRange& range, CSSParserMode mode)
+{
+ if (range.atEnd())
+ return nullptr;
+
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ auto firstRegion = DashboardRegion::create();
+ DashboardRegion* region = nullptr;
+
+ bool requireCommas = false;
+
+ while (!range.atEnd()) {
+ if (!region)
+ region = firstRegion.ptr();
+ else {
+ auto nextRegion = DashboardRegion::create();
+ region->m_next = nextRegion.copyRef();
+ region = nextRegion.ptr();
+ }
+
+ if (range.peek().functionId() != CSSValueDashboardRegion)
+ return nullptr;
+
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+ if (rangeCopy.end() == args.end())
+ return nullptr; // No ) was found. Be strict about this, since tests are.
+
+ // First arg is a label.
+ if (args.peek().type() != IdentToken)
+ return nullptr;
+ region->m_label = args.consumeIncludingWhitespace().value().toString();
+
+ // Comma is optional, so don't fail if we can't consume one.
+ requireCommas = consumeCommaIncludingWhitespace(args);
+
+ // Second arg is a type.
+ if (args.peek().type() != IdentToken)
+ return nullptr;
+ region->m_geometryType = args.consumeIncludingWhitespace().value().toString();
+ if (equalLettersIgnoringASCIICase(region->m_geometryType, "circle"))
+ region->m_isCircle = true;
+ else if (equalLettersIgnoringASCIICase(region->m_geometryType, "rectangle"))
+ region->m_isRectangle = true;
+ else
+ return nullptr;
+
+ if (args.atEnd()) {
+ // This originally used CSSValueInvalid by accident. It might be more logical to use something else.
+ RefPtr<CSSPrimitiveValue> amount = CSSValuePool::singleton().createIdentifierValue(CSSValueInvalid);
+ region->setTop(amount.copyRef());
+ region->setRight(amount.copyRef());
+ region->setBottom(amount.copyRef());
+ region->setLeft(WTFMove(amount));
+ range = rangeCopy;
+ continue;
+ }
+
+ // Next four arguments must be offset numbers or auto.
+ for (int i = 0; i < 4; ++i) {
+ if (args.atEnd() || (requireCommas && !consumeCommaIncludingWhitespace(args)))
+ return nullptr;
+
+ if (args.atEnd())
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> amount;
+ if (args.peek().id() == CSSValueAuto)
+ amount = consumeIdent(args);
+ else
+ amount = consumeLength(args, mode, ValueRangeAll);
+
+ if (!i)
+ region->setTop(WTFMove(amount));
+ else if (i == 1)
+ region->setRight(WTFMove(amount));
+ else if (i == 2)
+ region->setBottom(WTFMove(amount));
+ else
+ region->setLeft(WTFMove(amount));
+ }
+
+ if (!args.atEnd())
+ return nullptr;
+
+ range = rangeCopy;
+ }
+
+ return CSSValuePool::singleton().createValue(RefPtr<DashboardRegion>(WTFMove(firstRegion)));
+}
+
+#endif
+
+RefPtr<CSSValue> CSSPropertyParser::parseSingleValue(CSSPropertyID property, CSSPropertyID currentShorthand)
+{
+ if (CSSParserFastPaths::isKeywordPropertyID(property)) {
+ if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(property, m_range.peek().id(), m_context.mode))
+ return nullptr;
+ return consumeIdent(m_range);
+ }
+ switch (property) {
+ case CSSPropertyWillChange:
+ return consumeWillChange(m_range);
+ case CSSPropertyPage:
+ return consumePage(m_range);
+ case CSSPropertyQuotes:
+ return consumeQuotes(m_range);
+ case CSSPropertyFontVariantCaps:
+ return consumeFontVariantCaps(m_range);
+ case CSSPropertyFontVariantLigatures:
+ return consumeFontVariantLigatures(m_range);
+ case CSSPropertyFontVariantNumeric:
+ return consumeFontVariantNumeric(m_range);
+ case CSSPropertyFontVariantEastAsian:
+ return consumeFontVariantEastAsian(m_range);
+ case CSSPropertyFontFeatureSettings:
+ return consumeFontFeatureSettings(m_range);
+ case CSSPropertyFontFamily:
+ return consumeFontFamily(m_range);
+ case CSSPropertyFontWeight:
+ return consumeFontWeight(m_range);
+ case CSSPropertyFontSynthesis:
+ return consumeFontSynthesis(m_range);
+#if ENABLE(VARIATION_FONTS)
+ case CSSPropertyFontVariationSettings:
+ return consumeFontVariationSettings(m_range);
+#endif
+ case CSSPropertyLetterSpacing:
+ return consumeLetterSpacing(m_range, m_context.mode);
+ case CSSPropertyWordSpacing:
+ return consumeWordSpacing(m_range, m_context.mode);
+ case CSSPropertyTabSize:
+ return consumeTabSize(m_range, m_context.mode);
+#if ENABLE(TEXT_AUTOSIZING)
+ case CSSPropertyWebkitTextSizeAdjust:
+ // FIXME: Support toggling the validation of this property via a runtime setting that is independent of
+ // whether isTextAutosizingEnabled() is true. We want to enable this property on iOS, when simulating
+ // a iOS device in Safari's responsive design mode and when optionally enabled in DRT/WTR. Otherwise,
+ // this property should be disabled by default.
+#if !PLATFORM(IOS)
+ if (!m_context.textAutosizingEnabled)
+ return nullptr;
+#endif
+ return consumeTextSizeAdjust(m_range, m_context.mode);
+#endif
+ case CSSPropertyFontSize:
+ return consumeFontSize(m_range, m_context.mode, UnitlessQuirk::Allow);
+ case CSSPropertyLineHeight:
+ return consumeLineHeight(m_range, m_context.mode);
+ case CSSPropertyWebkitBorderHorizontalSpacing:
+ case CSSPropertyWebkitBorderVerticalSpacing:
+ return consumeLength(m_range, m_context.mode, ValueRangeNonNegative);
+ case CSSPropertyCounterIncrement:
+ case CSSPropertyCounterReset:
+ return consumeCounter(m_range, property == CSSPropertyCounterIncrement ? 1 : 0);
+ case CSSPropertySize:
+ return consumeSize(m_range, m_context.mode);
+ case CSSPropertyTextIndent:
+ return consumeTextIndent(m_range, m_context.mode);
+ case CSSPropertyMaxWidth:
+ case CSSPropertyMaxHeight:
+ return consumeMaxWidthOrHeight(m_range, m_context, UnitlessQuirk::Allow);
+ case CSSPropertyWebkitMaxLogicalWidth:
+ case CSSPropertyWebkitMaxLogicalHeight:
+ return consumeMaxWidthOrHeight(m_range, m_context);
+ case CSSPropertyMinWidth:
+ case CSSPropertyMinHeight:
+ case CSSPropertyWidth:
+ case CSSPropertyHeight:
+ return consumeWidthOrHeight(m_range, m_context, UnitlessQuirk::Allow);
+ case CSSPropertyWebkitMinLogicalWidth:
+ case CSSPropertyWebkitMinLogicalHeight:
+ case CSSPropertyWebkitLogicalWidth:
+ case CSSPropertyWebkitLogicalHeight:
+ return consumeWidthOrHeight(m_range, m_context);
+ case CSSPropertyMarginTop:
+ case CSSPropertyMarginRight:
+ case CSSPropertyMarginBottom:
+ case CSSPropertyMarginLeft:
+ case CSSPropertyBottom:
+ case CSSPropertyLeft:
+ case CSSPropertyRight:
+ case CSSPropertyTop:
+ return consumeMarginOrOffset(m_range, m_context.mode, UnitlessQuirk::Allow);
+ case CSSPropertyWebkitMarginStart:
+ case CSSPropertyWebkitMarginEnd:
+ case CSSPropertyWebkitMarginBefore:
+ case CSSPropertyWebkitMarginAfter:
+ return consumeMarginOrOffset(m_range, m_context.mode, UnitlessQuirk::Forbid);
+ case CSSPropertyPaddingTop:
+ case CSSPropertyPaddingRight:
+ case CSSPropertyPaddingBottom:
+ case CSSPropertyPaddingLeft:
+ return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow);
+ case CSSPropertyWebkitPaddingStart:
+ case CSSPropertyWebkitPaddingEnd:
+ case CSSPropertyWebkitPaddingBefore:
+ case CSSPropertyWebkitPaddingAfter:
+ return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Forbid);
+#if ENABLE(CSS_SCROLL_SNAP)
+ case CSSPropertyScrollSnapMarginBottom:
+ case CSSPropertyScrollSnapMarginLeft:
+ case CSSPropertyScrollSnapMarginRight:
+ case CSSPropertyScrollSnapMarginTop:
+ return consumeLength(m_range, m_context.mode, ValueRangeAll);
+ case CSSPropertyScrollPaddingBottom:
+ case CSSPropertyScrollPaddingLeft:
+ case CSSPropertyScrollPaddingRight:
+ case CSSPropertyScrollPaddingTop:
+ return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeAll);
+ case CSSPropertyScrollSnapAlign:
+ return consumeScrollSnapAlign(m_range);
+ case CSSPropertyScrollSnapType:
+ return consumeScrollSnapType(m_range);
+#endif
+ case CSSPropertyClip:
+ return consumeClip(m_range, m_context.mode);
+#if ENABLE(TOUCH_EVENTS)
+ case CSSPropertyTouchAction:
+ return consumeTouchAction(m_range);
+#endif
+ case CSSPropertyObjectPosition:
+ return consumePosition(m_range, m_context.mode, UnitlessQuirk::Forbid);
+ case CSSPropertyWebkitLineClamp:
+ return consumeLineClamp(m_range);
+ case CSSPropertyWebkitFontSizeDelta:
+ return consumeLength(m_range, m_context.mode, ValueRangeAll, UnitlessQuirk::Allow);
+ case CSSPropertyWebkitHyphenateCharacter:
+ case CSSPropertyWebkitLocale:
+ return consumeLocale(m_range);
+ case CSSPropertyWebkitHyphenateLimitBefore:
+ case CSSPropertyWebkitHyphenateLimitAfter:
+ return consumeHyphenateLimit(m_range, CSSValueAuto);
+ case CSSPropertyWebkitHyphenateLimitLines:
+ return consumeHyphenateLimit(m_range, CSSValueNoLimit);
+ case CSSPropertyColumnWidth:
+ return consumeColumnWidth(m_range);
+ case CSSPropertyColumnCount:
+ return consumeColumnCount(m_range);
+ case CSSPropertyColumnGap:
+ return consumeColumnGap(m_range, m_context.mode);
+ case CSSPropertyColumnSpan:
+ return consumeColumnSpan(m_range);
+ case CSSPropertyZoom:
+ return consumeZoom(m_range, m_context);
+ case CSSPropertyAnimationDelay:
+ case CSSPropertyTransitionDelay:
+ case CSSPropertyAnimationDirection:
+ case CSSPropertyAnimationDuration:
+ case CSSPropertyTransitionDuration:
+ case CSSPropertyAnimationFillMode:
+ case CSSPropertyAnimationIterationCount:
+ case CSSPropertyAnimationName:
+ case CSSPropertyAnimationPlayState:
+ case CSSPropertyTransitionProperty:
+ case CSSPropertyAnimationTimingFunction:
+ case CSSPropertyTransitionTimingFunction:
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+ case CSSPropertyWebkitAnimationTrigger:
+#endif
+ return consumeAnimationPropertyList(property, m_range, m_context);
+ case CSSPropertyGridColumnGap:
+ case CSSPropertyGridRowGap:
+ return consumeLength(m_range, m_context.mode, ValueRangeNonNegative);
+ case CSSPropertyShapeMargin:
+ return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative);
+ case CSSPropertyShapeImageThreshold:
+ return consumeNumber(m_range, ValueRangeAll);
+ case CSSPropertyWebkitBoxOrdinalGroup:
+ case CSSPropertyOrphans:
+ case CSSPropertyWidows:
+ return consumePositiveInteger(m_range);
+ case CSSPropertyWebkitTextDecorationColor:
+ return consumeColor(m_range, m_context.mode);
+ case CSSPropertyWebkitTextDecorationSkip:
+ return consumeTextDecorationSkip(m_range);
+ case CSSPropertyWebkitTextStrokeWidth:
+ return consumeTextStrokeWidth(m_range, m_context.mode);
+ case CSSPropertyWebkitTextFillColor:
+#if ENABLE(TOUCH_EVENTS)
+ case CSSPropertyWebkitTapHighlightColor:
+#endif
+ case CSSPropertyWebkitTextEmphasisColor:
+ case CSSPropertyWebkitBorderStartColor:
+ case CSSPropertyWebkitBorderEndColor:
+ case CSSPropertyWebkitBorderBeforeColor:
+ case CSSPropertyWebkitBorderAfterColor:
+ case CSSPropertyWebkitTextStrokeColor:
+ case CSSPropertyStopColor:
+ case CSSPropertyFloodColor:
+ case CSSPropertyLightingColor:
+ case CSSPropertyColumnRuleColor:
+ return consumeColor(m_range, m_context.mode);
+ case CSSPropertyColor:
+ case CSSPropertyBackgroundColor:
+ return consumeColor(m_range, m_context.mode, inQuirksMode());
+ case CSSPropertyWebkitBorderStartWidth:
+ case CSSPropertyWebkitBorderEndWidth:
+ case CSSPropertyWebkitBorderBeforeWidth:
+ case CSSPropertyWebkitBorderAfterWidth:
+ return consumeBorderWidth(m_range, m_context.mode, UnitlessQuirk::Forbid);
+ case CSSPropertyBorderBottomColor:
+ case CSSPropertyBorderLeftColor:
+ case CSSPropertyBorderRightColor:
+ case CSSPropertyBorderTopColor: {
+ bool allowQuirkyColors = inQuirksMode()
+ && (currentShorthand == CSSPropertyInvalid || currentShorthand == CSSPropertyBorderColor);
+ return consumeColor(m_range, m_context.mode, allowQuirkyColors);
+ }
+ case CSSPropertyBorderBottomWidth:
+ case CSSPropertyBorderLeftWidth:
+ case CSSPropertyBorderRightWidth:
+ case CSSPropertyBorderTopWidth: {
+ bool allowQuirkyLengths = inQuirksMode()
+ && (currentShorthand == CSSPropertyInvalid || currentShorthand == CSSPropertyBorderWidth);
+ UnitlessQuirk unitless = allowQuirkyLengths ? UnitlessQuirk::Allow : UnitlessQuirk::Forbid;
+ return consumeBorderWidth(m_range, m_context.mode, unitless);
+ }
+ case CSSPropertyZIndex:
+ return consumeZIndex(m_range);
+ case CSSPropertyTextShadow: // CSS2 property, dropped in CSS2.1, back in CSS3, so treat as CSS3
+ case CSSPropertyBoxShadow:
+ case CSSPropertyWebkitBoxShadow:
+ case CSSPropertyWebkitSvgShadow:
+ return consumeShadow(m_range, m_context.mode, property == CSSPropertyBoxShadow || property == CSSPropertyWebkitBoxShadow);
+ case CSSPropertyFilter:
+#if ENABLE(FILTERS_LEVEL_2)
+ case CSSPropertyWebkitBackdropFilter:
+#endif
+ return consumeFilter(m_range, m_context);
+ case CSSPropertyTextDecoration:
+ case CSSPropertyWebkitTextDecorationsInEffect:
+ case CSSPropertyWebkitTextDecorationLine:
+ return consumeTextDecorationLine(m_range);
+ case CSSPropertyWebkitTextEmphasisStyle:
+ return consumeTextEmphasisStyle(m_range);
+ case CSSPropertyOutlineColor:
+ return consumeOutlineColor(m_range, m_context.mode);
+ case CSSPropertyOutlineOffset:
+ return consumeLength(m_range, m_context.mode, ValueRangeAll);
+ case CSSPropertyOutlineWidth:
+ return consumeLineWidth(m_range, m_context.mode, UnitlessQuirk::Forbid);
+ case CSSPropertyTransform:
+ return consumeTransform(m_range, m_context.mode);
+ case CSSPropertyTransformOriginX:
+ case CSSPropertyPerspectiveOriginX:
+ return consumePositionX(m_range, m_context.mode);
+ case CSSPropertyTransformOriginY:
+ case CSSPropertyPerspectiveOriginY:
+ return consumePositionY(m_range, m_context.mode);
+ case CSSPropertyTransformOriginZ:
+ return consumeLength(m_range, m_context.mode, ValueRangeAll);
+ case CSSPropertyFill:
+ case CSSPropertyStroke:
+ return consumePaintStroke(m_range, m_context.mode);
+ case CSSPropertyGlyphOrientationVertical:
+ case CSSPropertyGlyphOrientationHorizontal:
+ return consumeGlyphOrientation(m_range, m_context.mode, property);
+ case CSSPropertyPaintOrder:
+ return consumePaintOrder(m_range);
+ case CSSPropertyMarkerStart:
+ case CSSPropertyMarkerMid:
+ case CSSPropertyMarkerEnd:
+ case CSSPropertyClipPath:
+ case CSSPropertyMask:
+ return consumeNoneOrURI(m_range);
+ case CSSPropertyFlexBasis:
+ return consumeFlexBasis(m_range, m_context.mode);
+ case CSSPropertyFlexGrow:
+ case CSSPropertyFlexShrink:
+ return consumeNumber(m_range, ValueRangeNonNegative);
+ case CSSPropertyStrokeDasharray:
+ return consumeStrokeDasharray(m_range);
+ case CSSPropertyColumnRuleWidth:
+ return consumeColumnRuleWidth(m_range, m_context.mode);
+ case CSSPropertyStrokeOpacity:
+ case CSSPropertyFillOpacity:
+ case CSSPropertyStopOpacity:
+ case CSSPropertyFloodOpacity:
+ case CSSPropertyOpacity:
+ case CSSPropertyWebkitBoxFlex:
+ return consumeNumber(m_range, ValueRangeAll);
+ case CSSPropertyBaselineShift:
+ return consumeBaselineShift(m_range);
+ case CSSPropertyKerning:
+ return consumeKerning(m_range, m_context.mode);
+ case CSSPropertyStrokeMiterlimit:
+ return consumeNumber(m_range, ValueRangeNonNegative);
+ case CSSPropertyStrokeWidth:
+ case CSSPropertyStrokeDashoffset:
+ case CSSPropertyCx:
+ case CSSPropertyCy:
+ case CSSPropertyX:
+ case CSSPropertyY:
+ case CSSPropertyR:
+ return consumeLengthOrPercent(m_range, SVGAttributeMode, ValueRangeAll, UnitlessQuirk::Forbid);
+ case CSSPropertyRx:
+ case CSSPropertyRy:
+ return consumeRxOrRy(m_range);
+ case CSSPropertyCursor:
+ return consumeCursor(m_range, m_context, inQuirksMode());
+ case CSSPropertyContent:
+ return consumeContent(m_range, m_context);
+ case CSSPropertyListStyleImage:
+ case CSSPropertyBorderImageSource:
+ case CSSPropertyWebkitMaskBoxImageSource:
+ return consumeImageOrNone(m_range, m_context);
+ case CSSPropertyPerspective:
+ return consumePerspective(m_range, m_context.mode);
+ case CSSPropertyBorderTopRightRadius:
+ case CSSPropertyBorderTopLeftRadius:
+ case CSSPropertyBorderBottomLeftRadius:
+ case CSSPropertyBorderBottomRightRadius:
+ return consumeBorderRadiusCorner(m_range, m_context.mode);
+ case CSSPropertyWebkitBoxFlexGroup:
+ return consumeInteger(m_range, 0);
+ case CSSPropertyOrder:
+ return consumeInteger(m_range);
+ case CSSPropertyWebkitTextUnderlinePosition:
+ // auto | alphabetic | [ under || [ left | right ] ], but we only support auto | alphabetic | under for now
+ return consumeIdent<CSSValueAuto, CSSValueUnder, CSSValueAlphabetic>(m_range);
+ case CSSPropertyVerticalAlign:
+ return consumeVerticalAlign(m_range, m_context.mode);
+ case CSSPropertyShapeOutside:
+ return consumeShapeOutside(m_range, m_context);
+ case CSSPropertyWebkitClipPath:
+ return consumeWebkitClipPath(m_range, m_context);
+ case CSSPropertyJustifyContent:
+ case CSSPropertyAlignContent:
+ return consumeContentDistributionOverflowPosition(m_range);
+ case CSSPropertyBorderImageRepeat:
+ case CSSPropertyWebkitMaskBoxImageRepeat:
+ return consumeBorderImageRepeat(m_range);
+ case CSSPropertyBorderImageSlice:
+ case CSSPropertyWebkitMaskBoxImageSlice:
+ return consumeBorderImageSlice(property, m_range);
+ case CSSPropertyBorderImageOutset:
+ case CSSPropertyWebkitMaskBoxImageOutset:
+ return consumeBorderImageOutset(m_range);
+ case CSSPropertyBorderImageWidth:
+ case CSSPropertyWebkitMaskBoxImageWidth:
+ return consumeBorderImageWidth(m_range);
+ case CSSPropertyWebkitBorderImage:
+ case CSSPropertyWebkitMaskBoxImage:
+ return consumeWebkitBorderImage(property, m_range, m_context);
+ case CSSPropertyWebkitBoxReflect:
+ return consumeReflect(m_range, m_context);
+ case CSSPropertyWebkitLineBoxContain:
+ return consumeLineBoxContain(m_range);
+#if ENABLE(CSS_IMAGE_ORIENTATION)
+ case CSSPropertyImageOrientation:
+ return consumeImageOrientation(m_range, m_context.mode);
+#endif
+ case CSSPropertyBackgroundAttachment:
+ case CSSPropertyBackgroundBlendMode:
+ case CSSPropertyBackgroundClip:
+ case CSSPropertyBackgroundImage:
+ case CSSPropertyBackgroundOrigin:
+ case CSSPropertyBackgroundPositionX:
+ case CSSPropertyBackgroundPositionY:
+ case CSSPropertyBackgroundSize:
+ case CSSPropertyWebkitBackgroundClip:
+ case CSSPropertyWebkitBackgroundOrigin:
+ case CSSPropertyWebkitBackgroundComposite:
+ case CSSPropertyWebkitBackgroundSize:
+ case CSSPropertyWebkitMaskClip:
+ case CSSPropertyWebkitMaskComposite:
+ case CSSPropertyWebkitMaskImage:
+ case CSSPropertyWebkitMaskOrigin:
+ case CSSPropertyWebkitMaskPositionX:
+ case CSSPropertyWebkitMaskPositionY:
+ case CSSPropertyWebkitMaskSize:
+ case CSSPropertyWebkitMaskSourceType:
+ return consumeCommaSeparatedBackgroundComponent(property, m_range, m_context);
+ case CSSPropertyWebkitMaskRepeatX:
+ case CSSPropertyWebkitMaskRepeatY:
+ return nullptr;
+ case CSSPropertyAlignItems:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeAlignItems(m_range);
+ case CSSPropertyJustifySelf:
+ case CSSPropertyAlignSelf:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeSelfPositionOverflowPosition(m_range);
+ case CSSPropertyJustifyItems:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeJustifyItems(m_range);
+ case CSSPropertyGridColumnEnd:
+ case CSSPropertyGridColumnStart:
+ case CSSPropertyGridRowEnd:
+ case CSSPropertyGridRowStart:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeGridLine(m_range);
+ case CSSPropertyGridAutoColumns:
+ case CSSPropertyGridAutoRows:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeGridTrackList(m_range, m_context.mode, GridAuto);
+ case CSSPropertyGridTemplateColumns:
+ case CSSPropertyGridTemplateRows:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeGridTemplatesRowsOrColumns(m_range, m_context.mode);
+ case CSSPropertyGridTemplateAreas:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeGridTemplateAreas(m_range);
+ case CSSPropertyGridAutoFlow:
+ if (!m_context.cssGridLayoutEnabled)
+ return nullptr;
+ return consumeGridAutoFlow(m_range);
+#if ENABLE(CSS_REGIONS)
+ case CSSPropertyWebkitFlowInto:
+ case CSSPropertyWebkitFlowFrom:
+ return consumeFlowProperty(m_range);
+#endif
+ case CSSPropertyWebkitLineGrid:
+ return consumeLineGrid(m_range);
+ case CSSPropertyWebkitInitialLetter:
+ return consumeInitialLetter(m_range);
+ case CSSPropertyHangingPunctuation:
+ return consumeHangingPunctuation(m_range);
+ case CSSPropertyWebkitMarqueeIncrement:
+ return consumeWebkitMarqueeIncrement(m_range, m_context.mode);
+ case CSSPropertyWebkitMarqueeRepetition:
+ return consumeWebkitMarqueeRepetition(m_range);
+ case CSSPropertyWebkitMarqueeSpeed:
+ return consumeWebkitMarqueeSpeed(m_range, m_context.mode);
+ case CSSPropertyAlt:
+ return consumeAlt(m_range, m_context);
+ case CSSPropertyWebkitAspectRatio:
+ return consumeWebkitAspectRatio(m_range);
+ case CSSPropertyWebkitTextEmphasisPosition:
+ return consumeTextEmphasisPosition(m_range);
+#if ENABLE(DASHBOARD_SUPPORT)
+ case CSSPropertyWebkitDashboardRegion:
+ return consumeWebkitDashboardRegion(m_range, m_context.mode);
+#endif
+ default:
+ return nullptr;
+ }
+}
+
+static RefPtr<CSSValueList> consumeFontFaceUnicodeRange(CSSParserTokenRange& range)
+{
+ RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated();
+
+ do {
+ const CSSParserToken& token = range.consumeIncludingWhitespace();
+ if (token.type() != UnicodeRangeToken)
+ return nullptr;
+
+ UChar32 start = token.unicodeRangeStart();
+ UChar32 end = token.unicodeRangeEnd();
+ if (start > end)
+ return nullptr;
+ values->append(CSSUnicodeRangeValue::create(start, end));
+ } while (consumeCommaIncludingWhitespace(range));
+
+ return values;
+}
+
+static RefPtr<CSSValue> consumeFontFaceSrcURI(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ String url = consumeUrlAsStringView(range).toString();
+ if (url.isNull())
+ return nullptr;
+
+ RefPtr<CSSFontFaceSrcValue> uriValue = CSSFontFaceSrcValue::create(context.completeURL(url));
+
+ if (range.peek().functionId() != CSSValueFormat)
+ return uriValue;
+
+ // FIXME: https://drafts.csswg.org/css-fonts says that format() contains a comma-separated list of strings,
+ // but CSSFontFaceSrcValue stores only one format. Allowing one format for now.
+ // FIXME: We're allowing the format to be an identifier as well as a string, because the old
+ // parser did. It's not clear if we need to continue to support this behavior, but we have lots of
+ // layout tests that rely on it.
+ CSSParserTokenRange args = consumeFunction(range);
+ const CSSParserToken& arg = args.consumeIncludingWhitespace();
+ if ((arg.type() != StringToken && arg.type() != IdentToken) || !args.atEnd())
+ return nullptr;
+ uriValue->setFormat(arg.value().toString());
+ return uriValue;
+}
+
+static RefPtr<CSSValue> consumeFontFaceSrcLocal(CSSParserTokenRange& range)
+{
+ CSSParserTokenRange args = consumeFunction(range);
+ if (args.peek().type() == StringToken) {
+ const CSSParserToken& arg = args.consumeIncludingWhitespace();
+ if (!args.atEnd())
+ return nullptr;
+ return CSSFontFaceSrcValue::createLocal(arg.value().toString());
+ }
+ if (args.peek().type() == IdentToken) {
+ String familyName = concatenateFamilyName(args);
+ if (!args.atEnd())
+ return nullptr;
+ return CSSFontFaceSrcValue::createLocal(familyName);
+ }
+ return nullptr;
+}
+
+static RefPtr<CSSValueList> consumeFontFaceSrc(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated();
+
+ do {
+ const CSSParserToken& token = range.peek();
+ RefPtr<CSSValue> parsedValue;
+ if (token.functionId() == CSSValueLocal)
+ parsedValue = consumeFontFaceSrcLocal(range);
+ else
+ parsedValue = consumeFontFaceSrcURI(range, context);
+ if (!parsedValue)
+ return nullptr;
+ values->append(parsedValue.releaseNonNull());
+ } while (consumeCommaIncludingWhitespace(range));
+ return values;
+}
+
+bool CSSPropertyParser::parseFontFaceDescriptor(CSSPropertyID propId)
+{
+ RefPtr<CSSValue> parsedValue;
+ switch (propId) {
+ case CSSPropertyFontFamily:
+ parsedValue = consumeFontFamilyDescriptor(m_range);
+ break;
+ case CSSPropertySrc: // This is a list of urls or local references.
+ parsedValue = consumeFontFaceSrc(m_range, m_context);
+ break;
+ case CSSPropertyUnicodeRange:
+ parsedValue = consumeFontFaceUnicodeRange(m_range);
+ break;
+ case CSSPropertyFontStretch:
+ case CSSPropertyFontStyle: {
+ CSSValueID id = m_range.consumeIncludingWhitespace().id();
+ if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(propId, id, m_context.mode))
+ return false;
+ parsedValue = CSSValuePool::singleton().createIdentifierValue(id);
+ break;
+ }
+ case CSSPropertyFontVariantCaps:
+ parsedValue = consumeFontVariantCaps(m_range);
+ break;
+ case CSSPropertyFontVariantLigatures:
+ parsedValue = consumeFontVariantLigatures(m_range);
+ break;
+ case CSSPropertyFontVariantNumeric:
+ parsedValue = consumeFontVariantNumeric(m_range);
+ break;
+ case CSSPropertyFontVariantEastAsian:
+ parsedValue = consumeFontVariantEastAsian(m_range);
+ break;
+ case CSSPropertyFontVariantAlternates:
+ parsedValue = consumeFontVariantAlternates(m_range);
+ break;
+ case CSSPropertyFontVariantPosition:
+ parsedValue = consumeFontVariantPosition(m_range);
+ break;
+ case CSSPropertyFontVariant:
+ return consumeFontVariantShorthand(false);
+ case CSSPropertyFontWeight:
+ parsedValue = consumeFontWeight(m_range);
+ break;
+ case CSSPropertyFontFeatureSettings:
+ parsedValue = consumeFontFeatureSettings(m_range);
+ break;
+ default:
+ break;
+ }
+
+ if (!parsedValue || !m_range.atEnd())
+ return false;
+
+ addProperty(propId, CSSPropertyInvalid, *parsedValue, false);
+ return true;
+}
+
+bool CSSPropertyParser::consumeSystemFont(bool important)
+{
+ CSSValueID systemFontID = m_range.consumeIncludingWhitespace().id();
+ ASSERT(systemFontID >= CSSValueCaption && systemFontID <= CSSValueStatusBar);
+ if (!m_range.atEnd())
+ return false;
+
+ FontCascadeDescription fontDescription;
+ RenderTheme::defaultTheme()->systemFont(systemFontID, fontDescription);
+ if (!fontDescription.isAbsoluteSize())
+ return false;
+
+ addProperty(CSSPropertyFontStyle, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(fontDescription.italic() == FontItalicOn ? CSSValueItalic : CSSValueNormal), important);
+ addProperty(CSSPropertyFontWeight, CSSPropertyFont, CSSValuePool::singleton().createValue(fontDescription.weight()), important);
+ addProperty(CSSPropertyFontSize, CSSPropertyFont, CSSValuePool::singleton().createValue(fontDescription.specifiedSize(), CSSPrimitiveValue::CSS_PX), important);
+ Ref<CSSValueList> fontFamilyList = CSSValueList::createCommaSeparated();
+ fontFamilyList->append(CSSValuePool::singleton().createFontFamilyValue(fontDescription.familyAt(0), FromSystemFontID::Yes));
+ addProperty(CSSPropertyFontFamily, CSSPropertyFont, WTFMove(fontFamilyList), important);
+ addProperty(CSSPropertyFontVariantCaps, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+ addProperty(CSSPropertyLineHeight, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+
+ // FIXME_NEWPARSER: What about FontVariantNumeric and FontVariantLigatures?
+
+ return true;
+}
+
+bool CSSPropertyParser::consumeFont(bool important)
+{
+ // Let's check if there is an inherit or initial somewhere in the shorthand.
+ CSSParserTokenRange range = m_range;
+ while (!range.atEnd()) {
+ CSSValueID id = range.consumeIncludingWhitespace().id();
+ if (id == CSSValueInherit || id == CSSValueInitial)
+ return false;
+ }
+ // Optional font-style, font-variant, font-stretch and font-weight.
+ RefPtr<CSSPrimitiveValue> fontStyle;
+ RefPtr<CSSPrimitiveValue> fontVariantCaps;
+ RefPtr<CSSPrimitiveValue> fontWeight;
+
+ // FIXME-NEWPARSER: Implement. RefPtr<CSSPrimitiveValue> fontStretch;
+ while (!m_range.atEnd()) {
+ CSSValueID id = m_range.peek().id();
+ if (!fontStyle && CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyFontStyle, id, m_context.mode)) {
+ fontStyle = consumeIdent(m_range);
+ continue;
+ }
+ if (!fontVariantCaps && (id == CSSValueNormal || id == CSSValueSmallCaps)) {
+ // Font variant in the shorthand is particular, it only accepts normal or small-caps.
+ // See https://drafts.csswg.org/css-fonts/#propdef-font
+ fontVariantCaps = consumeFontVariantCSS21(m_range);
+ if (fontVariantCaps)
+ continue;
+ }
+ if (!fontWeight) {
+ fontWeight = consumeFontWeight(m_range);
+ if (fontWeight)
+ continue;
+ }
+ /* FIXME-NEWPARSER: Implement
+ if (!fontStretch && CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyFontStretch, id, m_context.mode, m_styleSheetContents))
+ fontStretch = consumeIdent(m_range);
+ else*/
+ break;
+ }
+
+ if (m_range.atEnd())
+ return false;
+
+ bool hasStyle = fontStyle;
+ bool hasVariant = fontVariantCaps;
+ bool hasWeight = fontWeight;
+
+ addProperty(CSSPropertyFontStyle, CSSPropertyFont, fontStyle ? fontStyle.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, !hasStyle);
+ addProperty(CSSPropertyFontVariantCaps, CSSPropertyFont, fontVariantCaps ? fontVariantCaps.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, !hasVariant);
+/*
+ // FIXME-NEWPARSER: What do we do with these? They aren't part of our fontShorthand().
+ addProperty(CSSPropertyFontVariantLigatures, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, true);
+ addProperty(CSSPropertyFontVariantNumeric, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, true);
+*/
+
+ addProperty(CSSPropertyFontWeight, CSSPropertyFont, fontWeight ? fontWeight.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, !hasWeight);
+
+ // Now a font size _must_ come.
+ RefPtr<CSSValue> fontSize = consumeFontSize(m_range, m_context.mode);
+ if (!fontSize || m_range.atEnd())
+ return false;
+
+ addProperty(CSSPropertyFontSize, CSSPropertyFont, *fontSize, important);
+
+ if (consumeSlashIncludingWhitespace(m_range)) {
+ RefPtr<CSSPrimitiveValue> lineHeight = consumeLineHeight(m_range, m_context.mode);
+ if (!lineHeight)
+ return false;
+ addProperty(CSSPropertyLineHeight, CSSPropertyFont, lineHeight.releaseNonNull(), important);
+ } else {
+ addProperty(CSSPropertyLineHeight, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, true);
+ }
+
+ // Font family must come now.
+ RefPtr<CSSValue> parsedFamilyValue = consumeFontFamily(m_range);
+ if (!parsedFamilyValue)
+ return false;
+
+ addProperty(CSSPropertyFontFamily, CSSPropertyFont, parsedFamilyValue.releaseNonNull(), important);
+
+ return m_range.atEnd();
+}
+
+bool CSSPropertyParser::consumeFontVariantShorthand(bool important)
+{
+ if (identMatches<CSSValueNormal, CSSValueNone>(m_range.peek().id())) {
+ addProperty(CSSPropertyFontVariantLigatures, CSSPropertyFontVariant, consumeIdent(m_range).releaseNonNull(), important);
+ addProperty(CSSPropertyFontVariantCaps, CSSPropertyFontVariant, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+ addProperty(CSSPropertyFontVariantEastAsian, CSSPropertyFontVariant, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+ addProperty(CSSPropertyFontVariantPosition, CSSPropertyFontVariant, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+ return m_range.atEnd();
+ }
+
+ RefPtr<CSSPrimitiveValue> capsValue;
+ RefPtr<CSSPrimitiveValue> alternatesValue;
+ RefPtr<CSSPrimitiveValue> positionValue;
+
+ RefPtr<CSSValue> eastAsianValue;
+ FontVariantLigaturesParser ligaturesParser;
+ FontVariantNumericParser numericParser;
+ do {
+ if (!capsValue) {
+ capsValue = consumeFontVariantCaps(m_range);
+ if (capsValue)
+ continue;
+ }
+
+ if (!positionValue) {
+ positionValue = consumeFontVariantPosition(m_range);
+ if (positionValue)
+ continue;
+ }
+
+ if (!alternatesValue) {
+ alternatesValue = consumeFontVariantAlternates(m_range);
+ if (alternatesValue)
+ continue;
+ }
+
+ FontVariantLigaturesParser::ParseResult ligaturesParseResult = ligaturesParser.consumeLigature(m_range);
+ FontVariantNumericParser::ParseResult numericParseResult = numericParser.consumeNumeric(m_range);
+ if (ligaturesParseResult == FontVariantLigaturesParser::ParseResult::ConsumedValue
+ || numericParseResult == FontVariantNumericParser::ParseResult::ConsumedValue)
+ continue;
+
+ if (ligaturesParseResult == FontVariantLigaturesParser::ParseResult::DisallowedValue
+ || numericParseResult == FontVariantNumericParser::ParseResult::DisallowedValue)
+ return false;
+
+ if (!eastAsianValue) {
+ eastAsianValue = consumeFontVariantEastAsian(m_range);
+ if (eastAsianValue)
+ continue;
+ }
+
+ // Saw some value that didn't match anything else.
+ return false;
+
+ } while (!m_range.atEnd());
+
+ addProperty(CSSPropertyFontVariantLigatures, CSSPropertyFontVariant, ligaturesParser.finalizeValue().releaseNonNull(), important);
+ addProperty(CSSPropertyFontVariantNumeric, CSSPropertyFontVariant, numericParser.finalizeValue().releaseNonNull(), important);
+ addProperty(CSSPropertyFontVariantCaps, CSSPropertyFontVariant, capsValue ? capsValue.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+ addProperty(CSSPropertyFontVariantAlternates, CSSPropertyFontVariant, alternatesValue ? alternatesValue.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+ addProperty(CSSPropertyFontVariantPosition, CSSPropertyFontVariant, positionValue ? positionValue.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important);
+
+ if (!eastAsianValue)
+ eastAsianValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal);
+ addProperty(CSSPropertyFontVariantEastAsian, CSSPropertyFontVariant, eastAsianValue.releaseNonNull(), important);
+
+ return true;
+}
+
+bool CSSPropertyParser::consumeBorderSpacing(bool important)
+{
+ RefPtr<CSSValue> horizontalSpacing = consumeLength(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow);
+ if (!horizontalSpacing)
+ return false;
+ RefPtr<CSSValue> verticalSpacing = horizontalSpacing;
+ if (!m_range.atEnd())
+ verticalSpacing = consumeLength(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow);
+ if (!verticalSpacing || !m_range.atEnd())
+ return false;
+ addProperty(CSSPropertyWebkitBorderHorizontalSpacing, CSSPropertyBorderSpacing, horizontalSpacing.releaseNonNull(), important);
+ addProperty(CSSPropertyWebkitBorderVerticalSpacing, CSSPropertyBorderSpacing, verticalSpacing.releaseNonNull(), important);
+ return true;
+}
+
+#if ENABLE(CSS_DEVICE_ADAPTATION)
+
+static RefPtr<CSSValue> consumeSingleViewportDescriptor(CSSParserTokenRange& range, CSSPropertyID propId, CSSParserMode cssParserMode)
+{
+ CSSValueID id = range.peek().id();
+ switch (propId) {
+ case CSSPropertyMinWidth:
+ case CSSPropertyMaxWidth:
+ case CSSPropertyMinHeight:
+ case CSSPropertyMaxHeight:
+ if (id == CSSValueAuto)
+ return consumeIdent(range);
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative);
+ case CSSPropertyMinZoom:
+ case CSSPropertyMaxZoom:
+ case CSSPropertyZoom: {
+ if (id == CSSValueAuto)
+ return consumeIdent(range);
+ RefPtr<CSSValue> parsedValue = consumeNumber(range, ValueRangeNonNegative);
+ if (parsedValue)
+ return parsedValue;
+ return consumePercent(range, ValueRangeNonNegative);
+ }
+ case CSSPropertyUserZoom:
+ return consumeIdent<CSSValueZoom, CSSValueFixed>(range);
+ case CSSPropertyOrientation:
+ return consumeIdent<CSSValueAuto, CSSValuePortrait, CSSValueLandscape>(range);
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ ASSERT_NOT_REACHED();
+ return nullptr;
+}
+
+bool CSSPropertyParser::parseViewportDescriptor(CSSPropertyID propId, bool important)
+{
+ switch (propId) {
+ case CSSPropertyWidth: {
+ RefPtr<CSSValue> minWidth = consumeSingleViewportDescriptor(m_range, CSSPropertyMinWidth, m_context.mode);
+ if (!minWidth)
+ return false;
+ RefPtr<CSSValue> maxWidth = minWidth;
+ if (!m_range.atEnd())
+ maxWidth = consumeSingleViewportDescriptor(m_range, CSSPropertyMaxWidth, m_context.mode);
+ if (!maxWidth || !m_range.atEnd())
+ return false;
+ addProperty(CSSPropertyMinWidth, CSSPropertyInvalid, *minWidth, important);
+ addProperty(CSSPropertyMaxWidth, CSSPropertyInvalid, *maxWidth, important);
+ return true;
+ }
+ case CSSPropertyHeight: {
+ RefPtr<CSSValue> minHeight = consumeSingleViewportDescriptor(m_range, CSSPropertyMinHeight, m_context.mode);
+ if (!minHeight)
+ return false;
+ RefPtr<CSSValue> maxHeight = minHeight;
+ if (!m_range.atEnd())
+ maxHeight = consumeSingleViewportDescriptor(m_range, CSSPropertyMaxHeight, m_context.mode);
+ if (!maxHeight || !m_range.atEnd())
+ return false;
+ addProperty(CSSPropertyMinHeight, CSSPropertyInvalid, *minHeight, important);
+ addProperty(CSSPropertyMaxHeight, CSSPropertyInvalid, *maxHeight, important);
+ return true;
+ }
+ case CSSPropertyMinWidth:
+ case CSSPropertyMaxWidth:
+ case CSSPropertyMinHeight:
+ case CSSPropertyMaxHeight:
+ case CSSPropertyMinZoom:
+ case CSSPropertyMaxZoom:
+ case CSSPropertyZoom:
+ case CSSPropertyUserZoom:
+ case CSSPropertyOrientation: {
+ RefPtr<CSSValue> parsedValue = consumeSingleViewportDescriptor(m_range, propId, m_context.mode);
+ if (!parsedValue || !m_range.atEnd())
+ return false;
+ addProperty(propId, CSSPropertyInvalid, parsedValue.releaseNonNull(), important);
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+#endif
+
+bool CSSPropertyParser::consumeColumns(bool important)
+{
+ RefPtr<CSSValue> columnWidth;
+ RefPtr<CSSValue> columnCount;
+ bool hasPendingExplicitAuto = false;
+
+ for (unsigned propertiesParsed = 0; propertiesParsed < 2 && !m_range.atEnd(); ++propertiesParsed) {
+ if (!propertiesParsed && m_range.peek().id() == CSSValueAuto) {
+ // 'auto' is a valid value for any of the two longhands, and at this point
+ // we don't know which one(s) it is meant for. We need to see if there are other values first.
+ consumeIdent(m_range);
+ hasPendingExplicitAuto = true;
+ } else {
+ if (!columnWidth) {
+ if ((columnWidth = consumeColumnWidth(m_range)))
+ continue;
+ }
+ if (!columnCount) {
+ if ((columnCount = consumeColumnCount(m_range)))
+ continue;
+ }
+ // If we didn't find at least one match, this is an invalid shorthand and we have to ignore it.
+ return false;
+ }
+ }
+
+ if (!m_range.atEnd())
+ return false;
+
+ // Any unassigned property at this point will become implicit 'auto'.
+ if (columnWidth)
+ addProperty(CSSPropertyColumnWidth, CSSPropertyInvalid, columnWidth.releaseNonNull(), important);
+ else {
+ addProperty(CSSPropertyColumnWidth, CSSPropertyInvalid, CSSValuePool::singleton().createIdentifierValue(CSSValueAuto), important, !hasPendingExplicitAuto /* implicit */);
+ hasPendingExplicitAuto = false;
+ }
+
+ if (columnCount)
+ addProperty(CSSPropertyColumnCount, CSSPropertyInvalid, columnCount.releaseNonNull(), important);
+ else
+ addProperty(CSSPropertyColumnCount, CSSPropertyInvalid, CSSValuePool::singleton().createIdentifierValue(CSSValueAuto), important, !hasPendingExplicitAuto /* implicit */);
+
+ return true;
+}
+
+bool CSSPropertyParser::consumeShorthandGreedily(const StylePropertyShorthand& shorthand, bool important)
+{
+ ASSERT(shorthand.length() <= 6); // Existing shorthands have at most 6 longhands.
+ RefPtr<CSSValue> longhands[6];
+ const CSSPropertyID* shorthandProperties = shorthand.properties();
+ do {
+ bool foundLonghand = false;
+ for (size_t i = 0; !foundLonghand && i < shorthand.length(); ++i) {
+ if (longhands[i])
+ continue;
+ longhands[i] = parseSingleValue(shorthandProperties[i], shorthand.id());
+ if (longhands[i])
+ foundLonghand = true;
+ }
+ if (!foundLonghand)
+ return false;
+ } while (!m_range.atEnd());
+
+ for (size_t i = 0; i < shorthand.length(); ++i) {
+ if (longhands[i])
+ addProperty(shorthandProperties[i], shorthand.id(), longhands[i].releaseNonNull(), important);
+ else
+ addProperty(shorthandProperties[i], shorthand.id(), CSSValuePool::singleton().createImplicitInitialValue(), important);
+ }
+ return true;
+}
+
+bool CSSPropertyParser::consumeFlex(bool important)
+{
+ static const double unsetValue = -1;
+ double flexGrow = unsetValue;
+ double flexShrink = unsetValue;
+ RefPtr<CSSPrimitiveValue> flexBasis;
+
+ if (m_range.peek().id() == CSSValueNone) {
+ flexGrow = 0;
+ flexShrink = 0;
+ flexBasis = CSSValuePool::singleton().createIdentifierValue(CSSValueAuto);
+ m_range.consumeIncludingWhitespace();
+ } else {
+ unsigned index = 0;
+ while (!m_range.atEnd() && index++ < 3) {
+ double num;
+ if (consumeNumberRaw(m_range, num)) {
+ if (num < 0)
+ return false;
+ if (flexGrow == unsetValue)
+ flexGrow = num;
+ else if (flexShrink == unsetValue)
+ flexShrink = num;
+ else if (!num) // flex only allows a basis of 0 (sans units) if flex-grow and flex-shrink values have already been set.
+ flexBasis = CSSPrimitiveValue::create(0, CSSPrimitiveValue::UnitType::CSS_PX);
+ else
+ return false;
+ } else if (!flexBasis) {
+ if (m_range.peek().id() == CSSValueAuto)
+ flexBasis = consumeIdent(m_range);
+ if (!flexBasis)
+ flexBasis = consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative);
+ if (index == 2 && !m_range.atEnd())
+ return false;
+ }
+ }
+ if (index == 0)
+ return false;
+ if (flexGrow == unsetValue)
+ flexGrow = 1;
+ if (flexShrink == unsetValue)
+ flexShrink = 1;
+ if (!flexBasis)
+ flexBasis = CSSPrimitiveValue::create(0, CSSPrimitiveValue::UnitType::CSS_PX);
+ }
+
+ if (!m_range.atEnd())
+ return false;
+ addProperty(CSSPropertyFlexGrow, CSSPropertyFlex, CSSPrimitiveValue::create(clampTo<float>(flexGrow), CSSPrimitiveValue::UnitType::CSS_NUMBER), important);
+ addProperty(CSSPropertyFlexShrink, CSSPropertyFlex, CSSPrimitiveValue::create(clampTo<float>(flexShrink), CSSPrimitiveValue::UnitType::CSS_NUMBER), important);
+ addProperty(CSSPropertyFlexBasis, CSSPropertyFlex, flexBasis.releaseNonNull(), important);
+ return true;
+}
+
+bool CSSPropertyParser::consumeBorder(bool important)
+{
+ RefPtr<CSSValue> width;
+ RefPtr<CSSValue> style;
+ RefPtr<CSSValue> color;
+
+ while (!width || !style || !color) {
+ if (!width) {
+ width = consumeLineWidth(m_range, m_context.mode, UnitlessQuirk::Forbid);
+ if (width)
+ continue;
+ }
+ if (!style) {
+ style = parseSingleValue(CSSPropertyBorderLeftStyle, CSSPropertyBorder);
+ if (style)
+ continue;
+ }
+ if (!color) {
+ color = consumeColor(m_range, m_context.mode);
+ if (color)
+ continue;
+ }
+ break;
+ }
+
+ if (!width && !style && !color)
+ return false;
+
+ if (!width)
+ width = CSSValuePool::singleton().createImplicitInitialValue();
+ if (!style)
+ style = CSSValuePool::singleton().createImplicitInitialValue();
+ if (!color)
+ color = CSSValuePool::singleton().createImplicitInitialValue();
+
+ addExpandedPropertyForValue(CSSPropertyBorderWidth, width.releaseNonNull(), important);
+ addExpandedPropertyForValue(CSSPropertyBorderStyle, style.releaseNonNull(), important);
+ addExpandedPropertyForValue(CSSPropertyBorderColor, color.releaseNonNull(), important);
+ addExpandedPropertyForValue(CSSPropertyBorderImage, CSSValuePool::singleton().createImplicitInitialValue(), important);
+
+ return m_range.atEnd();
+}
+
+bool CSSPropertyParser::consume4Values(const StylePropertyShorthand& shorthand, bool important)
+{
+ ASSERT(shorthand.length() == 4);
+ const CSSPropertyID* longhands = shorthand.properties();
+ RefPtr<CSSValue> top = parseSingleValue(longhands[0], shorthand.id());
+ if (!top)
+ return false;
+
+ RefPtr<CSSValue> right = parseSingleValue(longhands[1], shorthand.id());
+ RefPtr<CSSValue> bottom;
+ RefPtr<CSSValue> left;
+ if (right) {
+ bottom = parseSingleValue(longhands[2], shorthand.id());
+ if (bottom)
+ left = parseSingleValue(longhands[3], shorthand.id());
+ }
+
+ bool rightImplicit = !right;
+ bool bottomImplicit = !bottom;
+ bool leftImplicit = !left;
+
+ if (!right)
+ right = top;
+ if (!bottom)
+ bottom = top;
+ if (!left)
+ left = right;
+
+ addProperty(longhands[0], shorthand.id(), top.releaseNonNull(), important);
+ addProperty(longhands[1], shorthand.id(), right.releaseNonNull(), important, rightImplicit);
+ addProperty(longhands[2], shorthand.id(), bottom.releaseNonNull(), important, bottomImplicit);
+ addProperty(longhands[3], shorthand.id(), left.releaseNonNull(), important, leftImplicit);
+
+ return m_range.atEnd();
+}
+
+bool CSSPropertyParser::consumeBorderImage(CSSPropertyID property, bool important)
+{
+ RefPtr<CSSValue> source;
+ RefPtr<CSSValue> slice;
+ RefPtr<CSSValue> width;
+ RefPtr<CSSValue> outset;
+ RefPtr<CSSValue> repeat;
+
+ if (consumeBorderImageComponents(property, m_range, m_context, source, slice, width, outset, repeat)) {
+ if (!source)
+ source = CSSValuePool::singleton().createImplicitInitialValue();
+ if (!slice)
+ slice = CSSValuePool::singleton().createImplicitInitialValue();
+ if (!width)
+ width = CSSValuePool::singleton().createImplicitInitialValue();
+ if (!outset)
+ outset = CSSValuePool::singleton().createImplicitInitialValue();
+ if (!repeat)
+ repeat = CSSValuePool::singleton().createImplicitInitialValue();
+ switch (property) {
+ case CSSPropertyWebkitMaskBoxImage:
+ addProperty(CSSPropertyWebkitMaskBoxImageSource, CSSPropertyWebkitMaskBoxImage, source.releaseNonNull(), important);
+ addProperty(CSSPropertyWebkitMaskBoxImageSlice, CSSPropertyWebkitMaskBoxImage, slice.releaseNonNull(), important);
+ addProperty(CSSPropertyWebkitMaskBoxImageWidth, CSSPropertyWebkitMaskBoxImage, width.releaseNonNull(), important);
+ addProperty(CSSPropertyWebkitMaskBoxImageOutset, CSSPropertyWebkitMaskBoxImage, outset.releaseNonNull(), important);
+ addProperty(CSSPropertyWebkitMaskBoxImageRepeat, CSSPropertyWebkitMaskBoxImage, repeat.releaseNonNull(), important);
+ return true;
+ case CSSPropertyBorderImage:
+ addProperty(CSSPropertyBorderImageSource, CSSPropertyBorderImage, source.releaseNonNull(), important);
+ addProperty(CSSPropertyBorderImageSlice, CSSPropertyBorderImage, slice.releaseNonNull(), important);
+ addProperty(CSSPropertyBorderImageWidth, CSSPropertyBorderImage, width.releaseNonNull() , important);
+ addProperty(CSSPropertyBorderImageOutset, CSSPropertyBorderImage, outset.releaseNonNull(), important);
+ addProperty(CSSPropertyBorderImageRepeat, CSSPropertyBorderImage, repeat.releaseNonNull(), important);
+ return true;
+ default:
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+ }
+ return false;
+}
+
+static inline CSSValueID mapFromPageBreakBetween(CSSValueID value)
+{
+ if (value == CSSValueAlways)
+ return CSSValuePage;
+ if (value == CSSValueAuto || value == CSSValueLeft || value == CSSValueRight)
+ return value;
+ if (value == CSSValueAvoid)
+ return CSSValueAvoidPage;
+ return CSSValueInvalid;
+}
+
+static inline CSSValueID mapFromColumnBreakBetween(CSSValueID value)
+{
+ if (value == CSSValueAlways)
+ return CSSValueColumn;
+ if (value == CSSValueAuto)
+ return value;
+ if (value == CSSValueAvoid)
+ return CSSValueAvoidColumn;
+ return CSSValueInvalid;
+}
+
+#if ENABLE(CSS_REGIONS)
+static inline CSSValueID mapFromRegionBreakBetween(CSSValueID value)
+{
+ if (value == CSSValueAlways)
+ return CSSValueRegion;
+ if (value == CSSValueAuto)
+ return value;
+ if (value == CSSValueAvoid)
+ return CSSValueAvoidRegion;
+ return CSSValueInvalid;
+}
+#endif
+
+static inline CSSValueID mapFromColumnRegionOrPageBreakInside(CSSValueID value)
+{
+ if (value == CSSValueAuto || value == CSSValueAvoid)
+ return value;
+ return CSSValueInvalid;
+}
+
+static inline CSSPropertyID mapFromLegacyBreakProperty(CSSPropertyID property)
+{
+ if (property == CSSPropertyPageBreakAfter || property == CSSPropertyWebkitColumnBreakAfter)
+ return CSSPropertyBreakAfter;
+ if (property == CSSPropertyPageBreakBefore || property == CSSPropertyWebkitColumnBreakBefore)
+ return CSSPropertyBreakBefore;
+#if ENABLE(CSS_REGIONS)
+ if (property == CSSPropertyWebkitRegionBreakAfter)
+ return CSSPropertyBreakAfter;
+ if (property == CSSPropertyWebkitRegionBreakBefore)
+ return CSSPropertyBreakBefore;
+ ASSERT(property == CSSPropertyPageBreakInside || property == CSSPropertyWebkitColumnBreakInside || property == CSSPropertyWebkitRegionBreakInside);
+#else
+ ASSERT(property == CSSPropertyPageBreakInside || property == CSSPropertyWebkitColumnBreakInside);
+#endif
+ return CSSPropertyBreakInside;
+}
+
+bool CSSPropertyParser::consumeLegacyBreakProperty(CSSPropertyID property, bool important)
+{
+ // The fragmentation spec says that page-break-(after|before|inside) are to be treated as
+ // shorthands for their break-(after|before|inside) counterparts. We'll do the same for the
+ // non-standard properties -webkit-column-break-(after|before|inside).
+ RefPtr<CSSPrimitiveValue> keyword = consumeIdent(m_range);
+ if (!keyword)
+ return false;
+ if (!m_range.atEnd())
+ return false;
+ CSSValueID value = keyword->valueID();
+ switch (property) {
+ case CSSPropertyPageBreakAfter:
+ case CSSPropertyPageBreakBefore:
+ value = mapFromPageBreakBetween(value);
+ break;
+ case CSSPropertyWebkitColumnBreakAfter:
+ case CSSPropertyWebkitColumnBreakBefore:
+ value = mapFromColumnBreakBetween(value);
+ break;
+#if ENABLE(CSS_REGIONS)
+ case CSSPropertyWebkitRegionBreakAfter:
+ case CSSPropertyWebkitRegionBreakBefore:
+ value = mapFromRegionBreakBetween(value);
+ break;
+ case CSSPropertyWebkitRegionBreakInside:
+#endif
+ case CSSPropertyPageBreakInside:
+ case CSSPropertyWebkitColumnBreakInside:
+ value = mapFromColumnRegionOrPageBreakInside(value);
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ if (value == CSSValueInvalid)
+ return false;
+
+ CSSPropertyID genericBreakProperty = mapFromLegacyBreakProperty(property);
+ addProperty(genericBreakProperty, property, CSSValuePool::singleton().createIdentifierValue(value), important);
+ return true;
+}
+
+static bool consumeBackgroundPosition(CSSParserTokenRange& range, const CSSParserContext& context, UnitlessQuirk unitless, RefPtr<CSSValue>& resultX, RefPtr<CSSValue>& resultY)
+{
+ do {
+ RefPtr<CSSPrimitiveValue> positionX;
+ RefPtr<CSSPrimitiveValue> positionY;
+ if (!consumePosition(range, context.mode, unitless, positionX, positionY))
+ return false;
+ addBackgroundValue(resultX, positionX.releaseNonNull());
+ addBackgroundValue(resultY, positionY.releaseNonNull());
+ } while (consumeCommaIncludingWhitespace(range));
+ return true;
+}
+
+static bool consumeRepeatStyleComponent(CSSParserTokenRange& range, RefPtr<CSSPrimitiveValue>& value1, RefPtr<CSSPrimitiveValue>& value2, bool& implicit)
+{
+ if (consumeIdent<CSSValueRepeatX>(range)) {
+ value1 = CSSValuePool::singleton().createIdentifierValue(CSSValueRepeat);
+ value2 = CSSValuePool::singleton().createIdentifierValue(CSSValueNoRepeat);
+ implicit = true;
+ return true;
+ }
+ if (consumeIdent<CSSValueRepeatY>(range)) {
+ value1 = CSSValuePool::singleton().createIdentifierValue(CSSValueNoRepeat);
+ value2 = CSSValuePool::singleton().createIdentifierValue(CSSValueRepeat);
+ implicit = true;
+ return true;
+ }
+ value1 = consumeIdent<CSSValueRepeat, CSSValueNoRepeat, CSSValueRound, CSSValueSpace>(range);
+ if (!value1)
+ return false;
+
+ value2 = consumeIdent<CSSValueRepeat, CSSValueNoRepeat, CSSValueRound, CSSValueSpace>(range);
+ if (!value2) {
+ value2 = value1;
+ implicit = true;
+ }
+ return true;
+}
+
+static bool consumeRepeatStyle(CSSParserTokenRange& range, RefPtr<CSSValue>& resultX, RefPtr<CSSValue>& resultY, bool& implicit)
+{
+ do {
+ RefPtr<CSSPrimitiveValue> repeatX;
+ RefPtr<CSSPrimitiveValue> repeatY;
+ if (!consumeRepeatStyleComponent(range, repeatX, repeatY, implicit))
+ return false;
+ addBackgroundValue(resultX, repeatX.releaseNonNull());
+ addBackgroundValue(resultY, repeatY.releaseNonNull());
+ } while (consumeCommaIncludingWhitespace(range));
+ return true;
+}
+
+// Note: consumeBackgroundShorthand assumes y properties (for example background-position-y) follow
+// the x properties in the shorthand array.
+bool CSSPropertyParser::consumeBackgroundShorthand(const StylePropertyShorthand& shorthand, bool important)
+{
+ const unsigned longhandCount = shorthand.length();
+ RefPtr<CSSValue> longhands[10];
+ ASSERT(longhandCount <= 10);
+
+ bool implicit = false;
+ do {
+ bool parsedLonghand[10] = { false };
+ RefPtr<CSSValue> originValue;
+ do {
+ bool foundProperty = false;
+ for (size_t i = 0; i < longhandCount; ++i) {
+ if (parsedLonghand[i])
+ continue;
+
+ RefPtr<CSSValue> value;
+ RefPtr<CSSValue> valueY;
+ CSSPropertyID property = shorthand.properties()[i];
+ if (property == CSSPropertyBackgroundRepeatX || property == CSSPropertyWebkitMaskRepeatX) {
+ RefPtr<CSSPrimitiveValue> primitiveValue;
+ RefPtr<CSSPrimitiveValue> primitiveValueY;
+ consumeRepeatStyleComponent(m_range, primitiveValue, primitiveValueY, implicit);
+ value = primitiveValue;
+ valueY = primitiveValueY;
+ } else if (property == CSSPropertyBackgroundPositionX || property == CSSPropertyWebkitMaskPositionX) {
+ CSSParserTokenRange rangeCopy = m_range;
+ RefPtr<CSSPrimitiveValue> primitiveValue;
+ RefPtr<CSSPrimitiveValue> primitiveValueY;
+ if (!consumePosition(rangeCopy, m_context.mode, UnitlessQuirk::Forbid, primitiveValue, primitiveValueY))
+ continue;
+ value = primitiveValue;
+ valueY = primitiveValueY;
+ m_range = rangeCopy;
+ } else if (property == CSSPropertyBackgroundSize || property == CSSPropertyWebkitMaskSize) {
+ if (!consumeSlashIncludingWhitespace(m_range))
+ continue;
+ value = consumeBackgroundSize(property, m_range, m_context.mode);
+ if (!value || !parsedLonghand[i - 1]) // Position must have been parsed in the current layer.
+ return false;
+ } else if (property == CSSPropertyBackgroundPositionY || property == CSSPropertyBackgroundRepeatY
+ || property == CSSPropertyWebkitMaskPositionY || property == CSSPropertyWebkitMaskRepeatY) {
+ continue;
+ } else {
+ value = consumeBackgroundComponent(property, m_range, m_context);
+ }
+ if (value) {
+ if (property == CSSPropertyBackgroundOrigin || property == CSSPropertyWebkitMaskOrigin)
+ originValue = value;
+ parsedLonghand[i] = true;
+ foundProperty = true;
+ addBackgroundValue(longhands[i], value.releaseNonNull());
+ if (valueY) {
+ parsedLonghand[i + 1] = true;
+ addBackgroundValue(longhands[i + 1], valueY.releaseNonNull());
+ }
+ }
+ }
+ if (!foundProperty)
+ return false;
+ } while (!m_range.atEnd() && m_range.peek().type() != CommaToken);
+
+ // FIXME: This will make invalid longhands, see crbug.com/386459
+ for (size_t i = 0; i < longhandCount; ++i) {
+ CSSPropertyID property = shorthand.properties()[i];
+ if (property == CSSPropertyBackgroundColor && !m_range.atEnd()) {
+ if (parsedLonghand[i])
+ return false; // Colors are only allowed in the last layer.
+ continue;
+ }
+ if ((property == CSSPropertyBackgroundClip || property == CSSPropertyWebkitMaskClip) && !parsedLonghand[i] && originValue) {
+ addBackgroundValue(longhands[i], originValue.releaseNonNull());
+ continue;
+ }
+ if (!parsedLonghand[i])
+ addBackgroundValue(longhands[i], CSSValuePool::singleton().createImplicitInitialValue());
+ }
+ } while (consumeCommaIncludingWhitespace(m_range));
+ if (!m_range.atEnd())
+ return false;
+
+ for (size_t i = 0; i < longhandCount; ++i) {
+ CSSPropertyID property = shorthand.properties()[i];
+ if (property == CSSPropertyBackgroundSize && longhands[i] && m_context.useLegacyBackgroundSizeShorthandBehavior)
+ continue;
+ addProperty(property, shorthand.id(), *longhands[i], important, implicit);
+ }
+ return true;
+}
+
+// FIXME-NEWPARSER: Hack to work around the fact that we aren't using CSSCustomIdentValue
+// for stuff yet. This can be replaced by CSSValue::isCustomIdentValue() once we switch
+// to using CSSCustomIdentValue everywhere.
+static bool isCustomIdentValue(const CSSValue& value)
+{
+ return is<CSSPrimitiveValue>(value) && downcast<CSSPrimitiveValue>(value).isString();
+}
+
+bool CSSPropertyParser::consumeGridItemPositionShorthand(CSSPropertyID shorthandId, bool important)
+{
+ const StylePropertyShorthand& shorthand = shorthandForProperty(shorthandId);
+ ASSERT(shorthand.length() == 2);
+ RefPtr<CSSValue> startValue = consumeGridLine(m_range);
+ if (!startValue)
+ return false;
+
+ RefPtr<CSSValue> endValue;
+ if (consumeSlashIncludingWhitespace(m_range)) {
+ endValue = consumeGridLine(m_range);
+ if (!endValue)
+ return false;
+ } else {
+ endValue = isCustomIdentValue(*startValue) ? startValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto);
+ }
+ if (!m_range.atEnd())
+ return false;
+ addProperty(shorthand.properties()[0], shorthandId, startValue.releaseNonNull(), important);
+ addProperty(shorthand.properties()[1], shorthandId, endValue.releaseNonNull(), important);
+ return true;
+}
+
+bool CSSPropertyParser::consumeGridAreaShorthand(bool important)
+{
+ RefPtr<CSSValue> rowStartValue = consumeGridLine(m_range);
+ if (!rowStartValue)
+ return false;
+ RefPtr<CSSValue> columnStartValue;
+ RefPtr<CSSValue> rowEndValue;
+ RefPtr<CSSValue> columnEndValue;
+ if (consumeSlashIncludingWhitespace(m_range)) {
+ columnStartValue = consumeGridLine(m_range);
+ if (!columnStartValue)
+ return false;
+ if (consumeSlashIncludingWhitespace(m_range)) {
+ rowEndValue = consumeGridLine(m_range);
+ if (!rowEndValue)
+ return false;
+ if (consumeSlashIncludingWhitespace(m_range)) {
+ columnEndValue = consumeGridLine(m_range);
+ if (!columnEndValue)
+ return false;
+ }
+ }
+ }
+ if (!m_range.atEnd())
+ return false;
+ if (!columnStartValue)
+ columnStartValue = isCustomIdentValue(*rowStartValue) ? rowStartValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto);
+ if (!rowEndValue)
+ rowEndValue = isCustomIdentValue(*rowStartValue) ? rowStartValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto);
+ if (!columnEndValue)
+ columnEndValue = isCustomIdentValue(*columnStartValue) ? columnStartValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto);
+
+ addProperty(CSSPropertyGridRowStart, CSSPropertyGridArea, rowStartValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridColumnStart, CSSPropertyGridArea, columnStartValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridRowEnd, CSSPropertyGridArea, rowEndValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridColumnEnd, CSSPropertyGridArea, columnEndValue.releaseNonNull(), important);
+ return true;
+}
+
+bool CSSPropertyParser::consumeGridTemplateRowsAndAreasAndColumns(CSSPropertyID shorthandId, bool important)
+{
+ NamedGridAreaMap gridAreaMap;
+ size_t rowCount = 0;
+ size_t columnCount = 0;
+ RefPtr<CSSValueList> templateRows = CSSValueList::createSpaceSeparated();
+
+ // Persists between loop iterations so we can use the same value for
+ // consecutive <line-names> values
+ RefPtr<CSSGridLineNamesValue> lineNames;
+
+ do {
+ // Handle leading <custom-ident>*.
+ bool hasPreviousLineNames = lineNames;
+ lineNames = consumeGridLineNames(m_range, lineNames.get());
+ if (lineNames && !hasPreviousLineNames)
+ templateRows->append(*lineNames);
+
+ // Handle a template-area's row.
+ if (m_range.peek().type() != StringToken || !parseGridTemplateAreasRow(m_range.consumeIncludingWhitespace().value().toString(), gridAreaMap, rowCount, columnCount))
+ return false;
+ ++rowCount;
+
+ // Handle template-rows's track-size.
+ RefPtr<CSSValue> value = consumeGridTrackSize(m_range, m_context.mode);
+ if (!value)
+ value = CSSValuePool::singleton().createIdentifierValue(CSSValueAuto);
+ templateRows->append(*value);
+
+ // This will handle the trailing/leading <custom-ident>* in the grammar.
+ lineNames = consumeGridLineNames(m_range);
+ if (lineNames)
+ templateRows->append(lineNames.releaseNonNull());
+ } while (!m_range.atEnd() && !(m_range.peek().type() == DelimiterToken && m_range.peek().delimiter() == '/'));
+
+ RefPtr<CSSValue> columnsValue;
+ if (!m_range.atEnd()) {
+ if (!consumeSlashIncludingWhitespace(m_range))
+ return false;
+ columnsValue = consumeGridTrackList(m_range, m_context.mode, GridTemplateNoRepeat);
+ if (!columnsValue || !m_range.atEnd())
+ return false;
+ } else {
+ columnsValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNone);
+ }
+ addProperty(CSSPropertyGridTemplateRows, shorthandId, templateRows.releaseNonNull(), important);
+ addProperty(CSSPropertyGridTemplateColumns, shorthandId, columnsValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridTemplateAreas, shorthandId, CSSGridTemplateAreasValue::create(gridAreaMap, rowCount, columnCount), important);
+ return true;
+}
+
+bool CSSPropertyParser::consumeGridTemplateShorthand(CSSPropertyID shorthandId, bool important)
+{
+ CSSParserTokenRange rangeCopy = m_range;
+ RefPtr<CSSValue> rowsValue = consumeIdent<CSSValueNone>(m_range);
+
+ // 1- 'none' case.
+ if (rowsValue && m_range.atEnd()) {
+ addProperty(CSSPropertyGridTemplateRows, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important);
+ addProperty(CSSPropertyGridTemplateColumns, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important);
+ addProperty(CSSPropertyGridTemplateAreas, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important);
+ return true;
+ }
+
+ // 2- <grid-template-rows> / <grid-template-columns>
+ if (!rowsValue)
+ rowsValue = consumeGridTrackList(m_range, m_context.mode, GridTemplate);
+
+ if (rowsValue) {
+ if (!consumeSlashIncludingWhitespace(m_range))
+ return false;
+ RefPtr<CSSValue> columnsValue = consumeGridTemplatesRowsOrColumns(m_range, m_context.mode);
+ if (!columnsValue || !m_range.atEnd())
+ return false;
+
+ addProperty(CSSPropertyGridTemplateRows, shorthandId, rowsValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridTemplateColumns, shorthandId, columnsValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridTemplateAreas, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important);
+ return true;
+ }
+
+ // 3- [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <track-list> ]?
+ m_range = rangeCopy;
+ return consumeGridTemplateRowsAndAreasAndColumns(shorthandId, important);
+}
+
+static RefPtr<CSSValue> consumeImplicitGridAutoFlow(CSSParserTokenRange& range, Ref<CSSPrimitiveValue>&& flowDirection)
+{
+ // [ auto-flow && dense? ]
+ if (range.atEnd())
+ return nullptr;
+ auto list = CSSValueList::createSpaceSeparated();
+ list->append(WTFMove(flowDirection));
+ if (range.peek().id() == CSSValueAutoFlow) {
+ range.consumeIncludingWhitespace();
+ RefPtr<CSSValue> denseIdent = consumeIdent<CSSValueDense>(range);
+ if (denseIdent)
+ list->append(denseIdent.releaseNonNull());
+ } else {
+ // Dense case
+ if (range.peek().id() != CSSValueDense)
+ return nullptr;
+ range.consumeIncludingWhitespace();
+ if (range.atEnd() || range.peek().id() != CSSValueAutoFlow)
+ return nullptr;
+ range.consumeIncludingWhitespace();
+ list->append(CSSValuePool::singleton().createIdentifierValue(CSSValueDense));
+ }
+
+ return WTFMove(list);
+}
+
+bool CSSPropertyParser::consumeGridShorthand(bool important)
+{
+ ASSERT(shorthandForProperty(CSSPropertyGrid).length() == 8);
+
+ CSSParserTokenRange rangeCopy = m_range;
+
+ // 1- <grid-template>
+ if (consumeGridTemplateShorthand(CSSPropertyGrid, important)) {
+ // It can only be specified the explicit or the implicit grid properties in a single grid declaration.
+ // The sub-properties not specified are set to their initial value, as normal for shorthands.
+ addProperty(CSSPropertyGridAutoFlow, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+ addProperty(CSSPropertyGridAutoColumns, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+ addProperty(CSSPropertyGridAutoRows, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+ addProperty(CSSPropertyGridColumnGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+ addProperty(CSSPropertyGridRowGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+ return true;
+ }
+
+ m_range = rangeCopy;
+
+ RefPtr<CSSValue> autoColumnsValue;
+ RefPtr<CSSValue> autoRowsValue;
+ RefPtr<CSSValue> templateRows;
+ RefPtr<CSSValue> templateColumns;
+ RefPtr<CSSValue> gridAutoFlow;
+
+ if (m_range.peek().id() == CSSValueAutoFlow || m_range.peek().id() == CSSValueDense) {
+ // 2- [ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>
+ gridAutoFlow = consumeImplicitGridAutoFlow(m_range, CSSValuePool::singleton().createIdentifierValue(CSSValueRow));
+ if (!gridAutoFlow || m_range.atEnd())
+ return false;
+ if (consumeSlashIncludingWhitespace(m_range))
+ autoRowsValue = CSSValuePool::singleton().createImplicitInitialValue();
+ else {
+ autoRowsValue = consumeGridTrackList(m_range, m_context.mode, GridAuto);
+ if (!autoRowsValue)
+ return false;
+ if (!consumeSlashIncludingWhitespace(m_range))
+ return false;
+ }
+ if (m_range.atEnd())
+ return false;
+ templateColumns = consumeGridTemplatesRowsOrColumns(m_range, m_context.mode);
+ if (!templateColumns)
+ return false;
+ templateRows = CSSValuePool::singleton().createImplicitInitialValue();
+ autoColumnsValue = CSSValuePool::singleton().createImplicitInitialValue();
+ } else {
+ // 3- <grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>?
+ templateRows = consumeGridTemplatesRowsOrColumns(m_range, m_context.mode);
+ if (!templateRows)
+ return false;
+ if (!consumeSlashIncludingWhitespace(m_range) || m_range.atEnd())
+ return false;
+ gridAutoFlow = consumeImplicitGridAutoFlow(m_range, CSSValuePool::singleton().createIdentifierValue(CSSValueColumn));
+ if (!gridAutoFlow)
+ return false;
+ if (m_range.atEnd())
+ autoColumnsValue = CSSValuePool::singleton().createImplicitInitialValue();
+ else {
+ autoColumnsValue = consumeGridTrackList(m_range, m_context.mode, GridAuto);
+ if (!autoColumnsValue)
+ return false;
+ }
+ templateColumns = CSSValuePool::singleton().createImplicitInitialValue();
+ autoRowsValue = CSSValuePool::singleton().createImplicitInitialValue();
+ }
+
+ if (!m_range.atEnd())
+ return false;
+
+ // It can only be specified the explicit or the implicit grid properties in a single grid declaration.
+ // The sub-properties not specified are set to their initial value, as normal for shorthands.
+ addProperty(CSSPropertyGridTemplateColumns, CSSPropertyGrid, templateColumns.releaseNonNull(), important);
+ addProperty(CSSPropertyGridTemplateRows, CSSPropertyGrid, templateRows.releaseNonNull(), important);
+ addProperty(CSSPropertyGridTemplateAreas, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+ addProperty(CSSPropertyGridAutoFlow, CSSPropertyGrid, gridAutoFlow.releaseNonNull(), important);
+ addProperty(CSSPropertyGridAutoColumns, CSSPropertyGrid, autoColumnsValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridAutoRows, CSSPropertyGrid, autoRowsValue.releaseNonNull(), important);
+ addProperty(CSSPropertyGridColumnGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+ addProperty(CSSPropertyGridRowGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important);
+
+ return true;
+}
+
+bool CSSPropertyParser::parseShorthand(CSSPropertyID property, bool important)
+{
+ switch (property) {
+ case CSSPropertyWebkitMarginCollapse: {
+ CSSValueID id = m_range.consumeIncludingWhitespace().id();
+ if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyWebkitMarginBeforeCollapse, id, m_context.mode))
+ return false;
+ addProperty(CSSPropertyWebkitMarginBeforeCollapse, CSSPropertyWebkitMarginCollapse, CSSValuePool::singleton().createIdentifierValue(id), important);
+ if (m_range.atEnd()) {
+ addProperty(CSSPropertyWebkitMarginAfterCollapse, CSSPropertyWebkitMarginCollapse, CSSValuePool::singleton().createIdentifierValue(id), important);
+ return true;
+ }
+ id = m_range.consumeIncludingWhitespace().id();
+ if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyWebkitMarginAfterCollapse, id, m_context.mode))
+ return false;
+ addProperty(CSSPropertyWebkitMarginAfterCollapse, CSSPropertyWebkitMarginCollapse, CSSValuePool::singleton().createIdentifierValue(id), important);
+ return true;
+ }
+ case CSSPropertyOverflow: {
+ CSSValueID id = m_range.consumeIncludingWhitespace().id();
+ if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyOverflowY, id, m_context.mode))
+ return false;
+ if (!m_range.atEnd())
+ return false;
+ RefPtr<CSSValue> overflowYValue = CSSValuePool::singleton().createIdentifierValue(id);
+ RefPtr<CSSValue> overflowXValue;
+
+ // FIXME: -webkit-paged-x or -webkit-paged-y only apply to overflow-y. If this value has been
+ // set using the shorthand, then for now overflow-x will default to auto, but once we implement
+ // pagination controls, it should default to hidden. If the overflow-y value is anything but
+ // paged-x or paged-y, then overflow-x and overflow-y should have the same value.
+ if (id == CSSValueWebkitPagedX || id == CSSValueWebkitPagedY)
+ overflowXValue = CSSValuePool::singleton().createIdentifierValue(CSSValueAuto);
+ else
+ overflowXValue = overflowYValue;
+ addProperty(CSSPropertyOverflowX, CSSPropertyOverflow, *overflowXValue, important);
+ addProperty(CSSPropertyOverflowY, CSSPropertyOverflow, *overflowYValue, important);
+ return true;
+ }
+ case CSSPropertyFont: {
+ const CSSParserToken& token = m_range.peek();
+ if (token.id() >= CSSValueCaption && token.id() <= CSSValueStatusBar)
+ return consumeSystemFont(important);
+ return consumeFont(important);
+ }
+ case CSSPropertyFontVariant:
+ return consumeFontVariantShorthand(important);
+ case CSSPropertyBorderSpacing:
+ return consumeBorderSpacing(important);
+ case CSSPropertyColumns:
+ return consumeColumns(important);
+ case CSSPropertyAnimation:
+ return consumeAnimationShorthand(animationShorthandForParsing(), important);
+ case CSSPropertyTransition:
+ return consumeAnimationShorthand(transitionShorthandForParsing(), important);
+ case CSSPropertyTextDecoration:
+ case CSSPropertyWebkitTextDecoration:
+ // FIXME-NEWPARSER: We need to unprefix -line/-style/-color ASAP and get rid
+ // of -webkit-text-decoration completely.
+ return consumeShorthandGreedily(webkitTextDecorationShorthand(), important);
+ case CSSPropertyMargin:
+ return consume4Values(marginShorthand(), important);
+ case CSSPropertyPadding:
+ return consume4Values(paddingShorthand(), important);
+#if ENABLE(CSS_SCROLL_SNAP)
+ case CSSPropertyScrollSnapMargin:
+ return consume4Values(scrollSnapMarginShorthand(), important);
+ case CSSPropertyScrollPadding:
+ return consume4Values(scrollPaddingShorthand(), important);
+#endif
+ case CSSPropertyWebkitTextEmphasis:
+ return consumeShorthandGreedily(webkitTextEmphasisShorthand(), important);
+ case CSSPropertyOutline:
+ return consumeShorthandGreedily(outlineShorthand(), important);
+ case CSSPropertyWebkitBorderStart:
+ return consumeShorthandGreedily(webkitBorderStartShorthand(), important);
+ case CSSPropertyWebkitBorderEnd:
+ return consumeShorthandGreedily(webkitBorderEndShorthand(), important);
+ case CSSPropertyWebkitBorderBefore:
+ return consumeShorthandGreedily(webkitBorderBeforeShorthand(), important);
+ case CSSPropertyWebkitBorderAfter:
+ return consumeShorthandGreedily(webkitBorderAfterShorthand(), important);
+ case CSSPropertyWebkitTextStroke:
+ return consumeShorthandGreedily(webkitTextStrokeShorthand(), important);
+ case CSSPropertyMarker: {
+ RefPtr<CSSValue> marker = parseSingleValue(CSSPropertyMarkerStart);
+ if (!marker || !m_range.atEnd())
+ return false;
+ auto markerRef = marker.releaseNonNull();
+ addProperty(CSSPropertyMarkerStart, CSSPropertyMarker, markerRef.copyRef(), important);
+ addProperty(CSSPropertyMarkerMid, CSSPropertyMarker, markerRef.copyRef(), important);
+ addProperty(CSSPropertyMarkerEnd, CSSPropertyMarker, markerRef.copyRef(), important);
+ return true;
+ }
+ case CSSPropertyFlex:
+ return consumeFlex(important);
+ case CSSPropertyFlexFlow:
+ return consumeShorthandGreedily(flexFlowShorthand(), important);
+ case CSSPropertyColumnRule:
+ return consumeShorthandGreedily(columnRuleShorthand(), important);
+ case CSSPropertyListStyle:
+ return consumeShorthandGreedily(listStyleShorthand(), important);
+ case CSSPropertyBorderRadius:
+ case CSSPropertyWebkitBorderRadius: {
+ RefPtr<CSSPrimitiveValue> horizontalRadii[4];
+ RefPtr<CSSPrimitiveValue> verticalRadii[4];
+ if (!consumeRadii(horizontalRadii, verticalRadii, m_range, m_context.mode, property == CSSPropertyWebkitBorderRadius))
+ return false;
+ addProperty(CSSPropertyBorderTopLeftRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[0].releaseNonNull(), verticalRadii[0].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important);
+ addProperty(CSSPropertyBorderTopRightRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[1].releaseNonNull(), verticalRadii[1].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important);
+ addProperty(CSSPropertyBorderBottomRightRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[2].releaseNonNull(), verticalRadii[2].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important);
+ addProperty(CSSPropertyBorderBottomLeftRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[3].releaseNonNull(), verticalRadii[3].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important);
+ return true;
+ }
+ case CSSPropertyBorderColor:
+ return consume4Values(borderColorShorthand(), important);
+ case CSSPropertyBorderStyle:
+ return consume4Values(borderStyleShorthand(), important);
+ case CSSPropertyBorderWidth:
+ return consume4Values(borderWidthShorthand(), important);
+ case CSSPropertyBorderTop:
+ return consumeShorthandGreedily(borderTopShorthand(), important);
+ case CSSPropertyBorderRight:
+ return consumeShorthandGreedily(borderRightShorthand(), important);
+ case CSSPropertyBorderBottom:
+ return consumeShorthandGreedily(borderBottomShorthand(), important);
+ case CSSPropertyBorderLeft:
+ return consumeShorthandGreedily(borderLeftShorthand(), important);
+ case CSSPropertyBorder:
+ return consumeBorder(important);
+ case CSSPropertyBorderImage:
+ return consumeBorderImage(property, important);
+ case CSSPropertyWebkitMaskPosition:
+ case CSSPropertyBackgroundPosition: {
+ RefPtr<CSSValue> resultX;
+ RefPtr<CSSValue> resultY;
+ if (!consumeBackgroundPosition(m_range, m_context, UnitlessQuirk::Allow, resultX, resultY) || !m_range.atEnd())
+ return false;
+ addProperty(property == CSSPropertyBackgroundPosition ? CSSPropertyBackgroundPositionX : CSSPropertyWebkitMaskPositionX, property, resultX.releaseNonNull(), important);
+ addProperty(property == CSSPropertyBackgroundPosition ? CSSPropertyBackgroundPositionY : CSSPropertyWebkitMaskPositionY, property, resultY.releaseNonNull(), important);
+ return true;
+ }
+ case CSSPropertyBackgroundRepeat:
+ case CSSPropertyWebkitMaskRepeat: {
+ RefPtr<CSSValue> resultX;
+ RefPtr<CSSValue> resultY;
+ bool implicit = false;
+ if (!consumeRepeatStyle(m_range, resultX, resultY, implicit) || !m_range.atEnd())
+ return false;
+ addProperty(property == CSSPropertyBackgroundRepeat ? CSSPropertyBackgroundRepeatX : CSSPropertyWebkitMaskRepeatX, property, resultX.releaseNonNull(), important, implicit);
+ addProperty(property == CSSPropertyBackgroundRepeat ? CSSPropertyBackgroundRepeatY : CSSPropertyWebkitMaskRepeatY, property, resultY.releaseNonNull(), important, implicit);
+ return true;
+ }
+ case CSSPropertyBackground:
+ return consumeBackgroundShorthand(backgroundShorthand(), important);
+ case CSSPropertyWebkitMask:
+ return consumeBackgroundShorthand(webkitMaskShorthand(), important);
+ case CSSPropertyTransformOrigin:
+ return consumeTransformOrigin(important);
+ case CSSPropertyPerspectiveOrigin:
+ return consumePerspectiveOrigin(important);
+ case CSSPropertyGridGap: {
+ RefPtr<CSSValue> rowGap = consumeLength(m_range, m_context.mode, ValueRangeNonNegative);
+ RefPtr<CSSValue> columnGap = consumeLength(m_range, m_context.mode, ValueRangeNonNegative);
+ if (!rowGap || !m_range.atEnd())
+ return false;
+ if (!columnGap)
+ columnGap = rowGap;
+ addProperty(CSSPropertyGridRowGap, CSSPropertyGridGap, rowGap.releaseNonNull(), important);
+ addProperty(CSSPropertyGridColumnGap, CSSPropertyGridGap, columnGap.releaseNonNull(), important);
+ return true;
+ }
+ case CSSPropertyGridColumn:
+ case CSSPropertyGridRow:
+ return consumeGridItemPositionShorthand(property, important);
+ case CSSPropertyGridArea:
+ return consumeGridAreaShorthand(important);
+ case CSSPropertyGridTemplate:
+ return consumeGridTemplateShorthand(CSSPropertyGridTemplate, important);
+ case CSSPropertyGrid:
+ return consumeGridShorthand(important);
+ case CSSPropertyWebkitMarquee:
+ return consumeShorthandGreedily(webkitMarqueeShorthand(), important);
+ default:
+ return false;
+ }
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSPropertyParser.h b/Source/WebCore/css/parser/CSSPropertyParser.h
new file mode 100644
index 000000000..76d3c712d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSPropertyParser.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2003 Lars Knoll (knoll@kde.org)
+ * Copyright (C) 2004, 2005, 2006, 2008, 2009, 2010, 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
+ * Copyright (C) 2009 - 2010 Torch Mobile (Beijing) Co. Ltd. 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.
+ */
+
+#pragma once
+
+#include "CSSParserTokenRange.h"
+#include "StyleRule.h"
+#include <wtf/text/StringView.h>
+
+namespace WebCore {
+
+class CSSProperty;
+class CSSValue;
+class StylePropertyShorthand;
+class StyleSheetContents;
+
+// Inputs: PropertyID, isImportant bool, CSSParserTokenRange.
+// Outputs: Vector of CSSProperties
+
+class CSSPropertyParser {
+ WTF_MAKE_NONCOPYABLE(CSSPropertyParser);
+public:
+ static bool parseValue(CSSPropertyID, bool important,
+ const CSSParserTokenRange&, const CSSParserContext&, StyleSheetContents*,
+ Vector<CSSProperty, 256>&, StyleRule::Type);
+
+ // Parses a non-shorthand CSS property
+ static RefPtr<CSSValue> parseSingleValue(CSSPropertyID, const CSSParserTokenRange&, const CSSParserContext&, StyleSheetContents*);
+
+private:
+ CSSPropertyParser(const CSSParserTokenRange&, const CSSParserContext&, StyleSheetContents*, Vector<CSSProperty, 256>*);
+
+ // FIXME: Rename once the CSSParserValue-based parseValue is removed
+ bool parseValueStart(CSSPropertyID, bool important);
+ bool consumeCSSWideKeyword(CSSPropertyID, bool important);
+ RefPtr<CSSValue> parseSingleValue(CSSPropertyID, CSSPropertyID = CSSPropertyInvalid);
+
+ bool inQuirksMode() const { return m_context.mode == HTMLQuirksMode; }
+
+ bool parseViewportDescriptor(CSSPropertyID propId, bool important);
+ bool parseFontFaceDescriptor(CSSPropertyID);
+
+ void addProperty(CSSPropertyID, CSSPropertyID, Ref<CSSValue>&&, bool important, bool implicit = false);
+ void addExpandedPropertyForValue(CSSPropertyID propId, Ref<CSSValue>&&, bool);
+
+ bool consumeBorder(bool important);
+
+ bool parseShorthand(CSSPropertyID, bool important);
+ bool consumeShorthandGreedily(const StylePropertyShorthand&, bool important);
+ bool consume4Values(const StylePropertyShorthand&, bool important);
+
+ // Legacy parsing allows <string>s for animation-name
+ bool consumeAnimationShorthand(const StylePropertyShorthand&, bool important);
+ bool consumeBackgroundShorthand(const StylePropertyShorthand&, bool important);
+
+ bool consumeColumns(bool important);
+
+ bool consumeGridItemPositionShorthand(CSSPropertyID, bool important);
+ bool consumeGridTemplateRowsAndAreasAndColumns(CSSPropertyID, bool important);
+ bool consumeGridTemplateShorthand(CSSPropertyID, bool important);
+ bool consumeGridShorthand(bool important);
+ bool consumeGridAreaShorthand(bool important);
+
+ bool consumeFont(bool important);
+ bool consumeFontVariantShorthand(bool important);
+ bool consumeSystemFont(bool important);
+
+ bool consumeBorderSpacing(bool important);
+
+ // CSS3 Parsing Routines (for properties specific to CSS3)
+ bool consumeBorderImage(CSSPropertyID, bool important);
+
+ bool consumeFlex(bool important);
+
+ bool consumeLegacyBreakProperty(CSSPropertyID, bool important);
+
+ bool consumeTransformOrigin(bool important);
+ bool consumePerspectiveOrigin(bool important);
+
+private:
+ // Inputs:
+ CSSParserTokenRange m_range;
+ const CSSParserContext& m_context;
+ StyleSheetContents* m_styleSheetContents;
+
+ // Outputs:
+ Vector<CSSProperty, 256>* m_parsedProperties;
+};
+
+CSSPropertyID cssPropertyID(StringView);
+CSSValueID cssValueKeywordID(StringView);
+bool isCustomPropertyName(const String&);
+
+#if PLATFORM(IOS)
+void cssPropertyNameIOSAliasing(const char* propertyName, const char*& propertyNameAlias, unsigned& newLength);
+#endif
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
new file mode 100644
index 000000000..a917ed28b
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
@@ -0,0 +1,1348 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSPropertyParserHelpers.h"
+
+#include "CSSCalculationValue.h"
+#include "CSSCanvasValue.h"
+#include "CSSCrossfadeValue.h"
+#include "CSSFilterImageValue.h"
+#include "CSSGradientValue.h"
+#include "CSSImageSetValue.h"
+#include "CSSImageValue.h"
+#include "CSSNamedImageValue.h"
+#include "CSSParserIdioms.h"
+#include "CSSValuePool.h"
+#include "Pair.h"
+#include "StyleColor.h"
+
+namespace WebCore {
+
+namespace CSSPropertyParserHelpers {
+
+bool consumeCommaIncludingWhitespace(CSSParserTokenRange& range)
+{
+ CSSParserToken value = range.peek();
+ if (value.type() != CommaToken)
+ return false;
+ range.consumeIncludingWhitespace();
+ return true;
+}
+
+bool consumeSlashIncludingWhitespace(CSSParserTokenRange& range)
+{
+ CSSParserToken value = range.peek();
+ if (value.type() != DelimiterToken || value.delimiter() != '/')
+ return false;
+ range.consumeIncludingWhitespace();
+ return true;
+}
+
+CSSParserTokenRange consumeFunction(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == FunctionToken);
+ CSSParserTokenRange contents = range.consumeBlock();
+ range.consumeWhitespace();
+ contents.consumeWhitespace();
+ return contents;
+}
+
+// FIXME: consider pulling in the parsing logic from CSSCalculationValue.cpp.
+class CalcParser {
+
+public:
+ explicit CalcParser(CSSParserTokenRange& range, ValueRange valueRange = ValueRangeAll)
+ : m_sourceRange(range)
+ , m_range(range)
+ {
+ const CSSParserToken& token = range.peek();
+ if (token.functionId() == CSSValueCalc || token.functionId() == CSSValueWebkitCalc)
+ m_calcValue = CSSCalcValue::create(consumeFunction(m_range), valueRange);
+ }
+
+ const CSSCalcValue* value() const { return m_calcValue.get(); }
+ RefPtr<CSSPrimitiveValue> consumeValue()
+ {
+ if (!m_calcValue)
+ return nullptr;
+ m_sourceRange = m_range;
+ return CSSValuePool::singleton().createValue(m_calcValue.release());
+ }
+ RefPtr<CSSPrimitiveValue> consumeNumber()
+ {
+ if (!m_calcValue)
+ return nullptr;
+ m_sourceRange = m_range;
+ return CSSValuePool::singleton().createValue(m_calcValue->doubleValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ }
+
+ bool consumeNumberRaw(double& result)
+ {
+ if (!m_calcValue || m_calcValue->category() != CalcNumber)
+ return false;
+ m_sourceRange = m_range;
+ result = m_calcValue->doubleValue();
+ return true;
+ }
+
+ bool consumePositiveIntegerRaw(int& result)
+ {
+ if (!m_calcValue || m_calcValue->category() != CalcNumber || !m_calcValue->isInt())
+ return false;
+ result = static_cast<int>(m_calcValue->doubleValue());
+ if (result < 1)
+ return false;
+ m_sourceRange = m_range;
+ return true;
+ }
+
+private:
+ CSSParserTokenRange& m_sourceRange;
+ CSSParserTokenRange m_range;
+ RefPtr<CSSCalcValue> m_calcValue;
+};
+
+RefPtr<CSSPrimitiveValue> consumeInteger(CSSParserTokenRange& range, double minimumValue)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == NumberToken) {
+ if (token.numericValueType() == NumberValueType || token.numericValue() < minimumValue)
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ }
+ CalcParser calcParser(range);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() != CalcNumber || !calculation->isInt())
+ return nullptr;
+ double value = calculation->doubleValue();
+ if (value < minimumValue)
+ return nullptr;
+ return calcParser.consumeNumber();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumePositiveInteger(CSSParserTokenRange& range)
+{
+ return consumeInteger(range, 1);
+}
+
+bool consumePositiveIntegerRaw(CSSParserTokenRange& range, int& result)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == NumberToken) {
+ if (token.numericValueType() == NumberValueType || token.numericValue() < 1)
+ return false;
+ result = range.consumeIncludingWhitespace().numericValue();
+ return true;
+ }
+ CalcParser calcParser(range);
+ return calcParser.consumePositiveIntegerRaw(result);
+}
+
+bool consumeNumberRaw(CSSParserTokenRange& range, double& result)
+{
+ if (range.peek().type() == NumberToken) {
+ result = range.consumeIncludingWhitespace().numericValue();
+ return true;
+ }
+ CalcParser calcParser(range, ValueRangeAll);
+ return calcParser.consumeNumberRaw(result);
+}
+
+// FIXME: Work out if this can just call consumeNumberRaw
+RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange& range, ValueRange valueRange)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == NumberToken) {
+ if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
+ }
+ CalcParser calcParser(range, ValueRangeAll);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ // FIXME: Calcs should not be subject to parse time range checks.
+ // spec: https://drafts.csswg.org/css-values-3/#calc-range
+ if (calculation->category() != CalcNumber || (valueRange == ValueRangeNonNegative && calculation->isNegative()))
+ return nullptr;
+ return calcParser.consumeNumber();
+ }
+ return nullptr;
+}
+
+inline bool shouldAcceptUnitlessValue(double value, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ // FIXME: Presentational HTML attributes shouldn't use the CSS parser for lengths
+ return value == 0
+ || isUnitLessValueParsingEnabledForMode(cssParserMode)
+ || (cssParserMode == HTMLQuirksMode && unitless == UnitlessQuirk::Allow);
+}
+
+RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == DimensionToken) {
+ switch (token.unitType()) {
+ case CSSPrimitiveValue::UnitType::CSS_QUIRKY_EMS:
+ if (cssParserMode != UASheetMode)
+ return nullptr;
+ FALLTHROUGH;
+ case CSSPrimitiveValue::UnitType::CSS_EMS:
+ case CSSPrimitiveValue::UnitType::CSS_REMS:
+ case CSSPrimitiveValue::UnitType::CSS_CHS:
+ case CSSPrimitiveValue::UnitType::CSS_EXS:
+ case CSSPrimitiveValue::UnitType::CSS_PX:
+ case CSSPrimitiveValue::UnitType::CSS_CM:
+ case CSSPrimitiveValue::UnitType::CSS_MM:
+ case CSSPrimitiveValue::UnitType::CSS_IN:
+ case CSSPrimitiveValue::UnitType::CSS_PT:
+ case CSSPrimitiveValue::UnitType::CSS_PC:
+ case CSSPrimitiveValue::UnitType::CSS_VW:
+ case CSSPrimitiveValue::UnitType::CSS_VH:
+ case CSSPrimitiveValue::UnitType::CSS_VMIN:
+ case CSSPrimitiveValue::UnitType::CSS_VMAX:
+ break;
+ default:
+ return nullptr;
+ }
+ if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
+ }
+ if (token.type() == NumberToken) {
+ if (!shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)
+ || (valueRange == ValueRangeNonNegative && token.numericValue() < 0))
+ return nullptr;
+ if (std::isinf(token.numericValue()))
+ return nullptr;
+ CSSPrimitiveValue::UnitType unitType = CSSPrimitiveValue::UnitType::CSS_PX;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unitType);
+ }
+ CalcParser calcParser(range, valueRange);
+ if (calcParser.value() && calcParser.value()->category() == CalcLength)
+ return calcParser.consumeValue();
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange& range, ValueRange valueRange)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == PercentageToken) {
+ if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ }
+ CalcParser calcParser(range, valueRange);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() == CalcPercent)
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+static bool canConsumeCalcValue(CalculationCategory category, CSSParserMode cssParserMode)
+{
+ if (category == CalcLength || category == CalcPercent || category == CalcPercentLength)
+ return true;
+
+ if (cssParserMode != SVGAttributeMode)
+ return false;
+
+ if (category == CalcNumber || category == CalcPercentNumber)
+ return true;
+
+ return false;
+}
+
+RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == DimensionToken || token.type() == NumberToken)
+ return consumeLength(range, cssParserMode, valueRange, unitless);
+ if (token.type() == PercentageToken)
+ return consumePercent(range, valueRange);
+ CalcParser calcParser(range, valueRange);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (canConsumeCalcValue(calculation->category(), cssParserMode))
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == DimensionToken) {
+ switch (token.unitType()) {
+ case CSSPrimitiveValue::UnitType::CSS_DEG:
+ case CSSPrimitiveValue::UnitType::CSS_RAD:
+ case CSSPrimitiveValue::UnitType::CSS_GRAD:
+ case CSSPrimitiveValue::UnitType::CSS_TURN:
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
+ default:
+ return nullptr;
+ }
+ }
+ if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)) {
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
+ }
+
+ CalcParser calcParser(range, ValueRangeAll);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() == CalcAngle)
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ CSSPrimitiveValue::UnitType unit = token.unitType();
+ bool acceptUnitless = token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless);
+ if (acceptUnitless)
+ unit = CSSPrimitiveValue::UnitType::CSS_MS;
+ if (token.type() == DimensionToken || acceptUnitless) {
+ if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
+ return nullptr;
+ if (unit == CSSPrimitiveValue::UnitType::CSS_MS || unit == CSSPrimitiveValue::UnitType::CSS_S)
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
+ return nullptr;
+ }
+ CalcParser calcParser(range, valueRange);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() == CalcTime)
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != IdentToken)
+ return nullptr;
+ return CSSValuePool::singleton().createIdentifierValue(range.consumeIncludingWhitespace().id());
+}
+
+RefPtr<CSSPrimitiveValue> consumeIdentRange(CSSParserTokenRange& range, CSSValueID lower, CSSValueID upper)
+{
+ if (range.peek().id() < lower || range.peek().id() > upper)
+ return nullptr;
+ return consumeIdent(range);
+}
+
+// FIXME-NEWPARSER: Eventually we'd like this to use CSSCustomIdentValue, but we need
+// to do other plumbing work first (like changing Pair to CSSValuePair and make it not
+// use only primitive values).
+RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != IdentToken || isCSSWideKeyword(range.peek().id()))
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
+}
+
+RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != StringToken)
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
+}
+
+StringView consumeUrlAsStringView(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == UrlToken) {
+ range.consumeIncludingWhitespace();
+ return token.value();
+ }
+ if (token.functionId() == CSSValueUrl) {
+ CSSParserTokenRange urlRange = range;
+ CSSParserTokenRange urlArgs = urlRange.consumeBlock();
+ const CSSParserToken& next = urlArgs.consumeIncludingWhitespace();
+ if (next.type() == BadStringToken || !urlArgs.atEnd())
+ return StringView();
+ ASSERT(next.type() == StringToken);
+ range = urlRange;
+ range.consumeWhitespace();
+ return next.value();
+ }
+
+ return StringView();
+}
+
+RefPtr<CSSPrimitiveValue> consumeUrl(CSSParserTokenRange& range)
+{
+ StringView url = consumeUrlAsStringView(range);
+ if (url.isNull())
+ return nullptr;
+ return CSSValuePool::singleton().createValue(url.toString(), CSSPrimitiveValue::UnitType::CSS_URI);
+}
+
+static int clampRGBComponent(const CSSPrimitiveValue& value)
+{
+ double result = value.doubleValue();
+ // FIXME: Multiply by 2.55 and round instead of floor.
+ if (value.isPercentage())
+ result *= 2.56;
+ return clampTo<int>(result, 0, 255);
+}
+
+static Color parseRGBParameters(CSSParserTokenRange& range, bool parseAlpha)
+{
+ ASSERT(range.peek().functionId() == CSSValueRgb || range.peek().functionId() == CSSValueRgba);
+ Color result;
+ CSSParserTokenRange args = consumeFunction(range);
+ RefPtr<CSSPrimitiveValue> colorParameter = consumeInteger(args);
+ if (!colorParameter)
+ colorParameter = consumePercent(args, ValueRangeAll);
+ if (!colorParameter)
+ return Color();
+ const bool isPercent = colorParameter->isPercentage();
+ int colorArray[3];
+ colorArray[0] = clampRGBComponent(*colorParameter);
+ for (int i = 1; i < 3; i++) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ colorParameter = isPercent ? consumePercent(args, ValueRangeAll) : consumeInteger(args);
+ if (!colorParameter)
+ return Color();
+ colorArray[i] = clampRGBComponent(*colorParameter);
+ }
+ if (parseAlpha) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ double alpha;
+ if (!consumeNumberRaw(args, alpha))
+ return Color();
+ // Convert the floating pointer number of alpha to an integer in the range [0, 256),
+ // with an equal distribution across all 256 values.
+ int alphaComponent = static_cast<int>(clampTo<double>(alpha, 0.0, 1.0) * nextafter(256.0, 0.0));
+ result = Color(makeRGBA(colorArray[0], colorArray[1], colorArray[2], alphaComponent));
+ } else {
+ result = Color(makeRGB(colorArray[0], colorArray[1], colorArray[2]));
+ }
+
+ if (!args.atEnd())
+ return Color();
+
+ return result;
+}
+
+static Color parseHSLParameters(CSSParserTokenRange& range, bool parseAlpha)
+{
+ ASSERT(range.peek().functionId() == CSSValueHsl || range.peek().functionId() == CSSValueHsla);
+ CSSParserTokenRange args = consumeFunction(range);
+ RefPtr<CSSPrimitiveValue> hslValue = consumeNumber(args, ValueRangeAll);
+ if (!hslValue)
+ return Color();
+ double colorArray[3];
+ colorArray[0] = (((hslValue->intValue() % 360) + 360) % 360) / 360.0;
+ for (int i = 1; i < 3; i++) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ hslValue = consumePercent(args, ValueRangeAll);
+ if (!hslValue)
+ return Color();
+ double doubleValue = hslValue->doubleValue();
+ colorArray[i] = clampTo<double>(doubleValue, 0.0, 100.0) / 100.0; // Needs to be value between 0 and 1.0.
+ }
+ double alpha = 1.0;
+ if (parseAlpha) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ if (!consumeNumberRaw(args, alpha))
+ return Color();
+ alpha = clampTo<double>(alpha, 0.0, 1.0);
+ }
+
+ if (!args.atEnd())
+ return Color();
+
+ return Color(makeRGBAFromHSLA(colorArray[0], colorArray[1], colorArray[2], alpha));
+}
+
+static Color parseColorFunctionParameters(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().functionId() == CSSValueColor);
+ CSSParserTokenRange args = consumeFunction(range);
+
+ ColorSpace colorSpace;
+ switch (args.peek().id()) {
+ case CSSValueSRGB:
+ colorSpace = ColorSpaceSRGB;
+ break;
+ case CSSValueDisplayP3:
+ colorSpace = ColorSpaceDisplayP3;
+ break;
+ default:
+ return Color();
+ }
+ consumeIdent(args);
+
+ double colorChannels[4] = { 0, 0, 0, 1 };
+ for (int i = 0; i < 3; ++i) {
+ double value;
+ if (consumeNumberRaw(args, value))
+ colorChannels[i] = std::max(0.0, std::min(1.0, value));
+ else
+ break;
+ }
+
+ if (consumeSlashIncludingWhitespace(args)) {
+ auto alphaParameter = consumePercent(args, ValueRangeAll);
+ if (!alphaParameter)
+ alphaParameter = consumeNumber(args, ValueRangeAll);
+ if (!alphaParameter)
+ return Color();
+
+ colorChannels[3] = std::max(0.0, std::min(1.0, alphaParameter->isPercentage() ? (alphaParameter->doubleValue() / 100) : alphaParameter->doubleValue()));
+ }
+
+ // FIXME: Support the comma-separated list of fallback color values.
+
+ if (!args.atEnd())
+ return Color();
+
+ return Color(colorChannels[0], colorChannels[1], colorChannels[2], colorChannels[3], colorSpace);
+}
+
+static Color parseHexColor(CSSParserTokenRange& range, bool acceptQuirkyColors)
+{
+ RGBA32 result;
+ const CSSParserToken& token = range.peek();
+ if (token.type() == HashToken) {
+ if (!Color::parseHexColor(token.value(), result))
+ return Color();
+ } else if (acceptQuirkyColors) {
+ String color;
+ if (token.type() == NumberToken || token.type() == DimensionToken) {
+ if (token.numericValueType() != IntegerValueType
+ || token.numericValue() < 0. || token.numericValue() >= 1000000.)
+ return Color();
+ if (token.type() == NumberToken) // e.g. 112233
+ color = String::format("%d", static_cast<int>(token.numericValue()));
+ else // e.g. 0001FF
+ color = String::number(static_cast<int>(token.numericValue())) + token.value().toString();
+ while (color.length() < 6)
+ color = "0" + color;
+ } else if (token.type() == IdentToken) { // e.g. FF0000
+ color = token.value().toString();
+ }
+ unsigned length = color.length();
+ if (length != 3 && length != 6)
+ return Color();
+ if (!Color::parseHexColor(color, result))
+ return Color();
+ } else {
+ return Color();
+ }
+ range.consumeIncludingWhitespace();
+ return Color(result);
+}
+
+static Color parseColorFunction(CSSParserTokenRange& range)
+{
+ CSSParserTokenRange colorRange = range;
+ CSSValueID functionId = range.peek().functionId();
+ Color color;
+ switch (functionId) {
+ case CSSValueRgb:
+ case CSSValueRgba:
+ color = parseRGBParameters(colorRange, functionId == CSSValueRgba);
+ break;
+ case CSSValueHsl:
+ case CSSValueHsla:
+ color = parseHSLParameters(colorRange, functionId == CSSValueHsla);
+ break;
+ case CSSValueColor:
+ color = parseColorFunctionParameters(colorRange);
+ break;
+ default:
+ return Color();
+ }
+ range = colorRange;
+ return color;
+}
+
+RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool acceptQuirkyColors)
+{
+ CSSValueID id = range.peek().id();
+ if (StyleColor::isColorKeyword(id)) {
+ if (!isValueAllowedInMode(id, cssParserMode))
+ return nullptr;
+ return consumeIdent(range);
+ }
+ Color color = parseHexColor(range, acceptQuirkyColors);
+ if (!color.isValid())
+ color = parseColorFunction(range);
+ if (!color.isValid())
+ return nullptr;
+ return CSSValuePool::singleton().createValue(color);
+}
+
+static RefPtr<CSSPrimitiveValue> consumePositionComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ if (range.peek().type() == IdentToken)
+ return consumeIdent<CSSValueLeft, CSSValueTop, CSSValueBottom, CSSValueRight, CSSValueCenter>(range);
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless);
+}
+
+static bool isHorizontalPositionKeywordOnly(const CSSPrimitiveValue& value)
+{
+ return value.isValueID() && (value.valueID() == CSSValueLeft || value.valueID() == CSSValueRight);
+}
+
+static bool isVerticalPositionKeywordOnly(const CSSPrimitiveValue& value)
+{
+ return value.isValueID() && (value.valueID() == CSSValueTop || value.valueID() == CSSValueBottom);
+}
+
+static void positionFromOneValue(CSSPrimitiveValue& value, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ bool valueAppliesToYAxisOnly = isVerticalPositionKeywordOnly(value);
+ resultX = &value;
+ resultY = CSSPrimitiveValue::createIdentifier(CSSValueCenter);
+ if (valueAppliesToYAxisOnly)
+ std::swap(resultX, resultY);
+}
+
+static bool positionFromTwoValues(CSSPrimitiveValue& value1, CSSPrimitiveValue& value2,
+ RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ bool mustOrderAsXY = isHorizontalPositionKeywordOnly(value1) || isVerticalPositionKeywordOnly(value2)
+ || !value1.isValueID() || !value2.isValueID();
+ bool mustOrderAsYX = isVerticalPositionKeywordOnly(value1) || isHorizontalPositionKeywordOnly(value2);
+ if (mustOrderAsXY && mustOrderAsYX)
+ return false;
+ resultX = &value1;
+ resultY = &value2;
+ if (mustOrderAsYX)
+ std::swap(resultX, resultY);
+ return true;
+}
+
+
+template<typename... Args>
+static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
+{
+ return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
+}
+
+static bool positionFromThreeOrFourValues(CSSPrimitiveValue** values, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ CSSPrimitiveValue* center = nullptr;
+ for (int i = 0; values[i]; i++) {
+ CSSPrimitiveValue* currentValue = values[i];
+ if (!currentValue->isValueID())
+ return false;
+ CSSValueID id = currentValue->valueID();
+
+ if (id == CSSValueCenter) {
+ if (center)
+ return false;
+ center = currentValue;
+ continue;
+ }
+
+ RefPtr<CSSPrimitiveValue> result;
+ if (values[i + 1] && !values[i + 1]->isValueID())
+ result = createPrimitiveValuePair(currentValue, values[++i]);
+ else
+ result = currentValue;
+
+ if (id == CSSValueLeft || id == CSSValueRight) {
+ if (resultX)
+ return false;
+ resultX = result;
+ } else {
+ ASSERT(id == CSSValueTop || id == CSSValueBottom);
+ if (resultY)
+ return false;
+ resultY = result;
+ }
+ }
+
+ if (center) {
+ ASSERT(resultX || resultY);
+ if (resultX && resultY)
+ return false;
+ if (!resultX)
+ resultX = center;
+ else
+ resultY = center;
+ }
+
+ ASSERT(resultX && resultY);
+ return true;
+}
+
+// FIXME: This may consume from the range upon failure. The background
+// shorthand works around it, but we should just fix it here.
+bool consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value1)
+ return false;
+
+ RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value2) {
+ positionFromOneValue(*value1, resultX, resultY);
+ return true;
+ }
+
+ RefPtr<CSSPrimitiveValue> value3 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value3)
+ return positionFromTwoValues(*value1, *value2, resultX, resultY);
+
+ RefPtr<CSSPrimitiveValue> value4 = consumePositionComponent(range, cssParserMode, unitless);
+ CSSPrimitiveValue* values[5];
+ values[0] = value1.get();
+ values[1] = value2.get();
+ values[2] = value3.get();
+ values[3] = value4.get();
+ values[4] = nullptr;
+ return positionFromThreeOrFourValues(values, resultX, resultY);
+}
+
+RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ RefPtr<CSSPrimitiveValue> resultX;
+ RefPtr<CSSPrimitiveValue> resultY;
+ if (consumePosition(range, cssParserMode, unitless, resultX, resultY))
+ return createPrimitiveValuePair(resultX.releaseNonNull(), resultY.releaseNonNull());
+ return nullptr;
+}
+
+bool consumeOneOrTwoValuedPosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value1)
+ return false;
+ RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value2) {
+ positionFromOneValue(*value1, resultX, resultY);
+ return true;
+ }
+ return positionFromTwoValues(*value1, *value2, resultX, resultY);
+}
+
+// This should go away once we drop support for -webkit-gradient
+static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientPoint(CSSParserTokenRange& args, bool horizontal)
+{
+ if (args.peek().type() == IdentToken) {
+ if ((horizontal && consumeIdent<CSSValueLeft>(args)) || (!horizontal && consumeIdent<CSSValueTop>(args)))
+ return CSSValuePool::singleton().createValue(0., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args)))
+ return CSSValuePool::singleton().createValue(100., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ if (consumeIdent<CSSValueCenter>(args))
+ return CSSValuePool::singleton().createValue(50., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ return nullptr;
+ }
+ RefPtr<CSSPrimitiveValue> result = consumePercent(args, ValueRangeAll);
+ if (!result)
+ result = consumeNumber(args, ValueRangeAll);
+ return result;
+}
+
+// Used to parse colors for -webkit-gradient(...).
+static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientStopColor(CSSParserTokenRange& args, CSSParserMode cssParserMode)
+{
+ if (args.peek().id() == CSSValueCurrentcolor)
+ return nullptr;
+ return consumeColor(args, cssParserMode);
+}
+
+static bool consumeDeprecatedGradientColorStop(CSSParserTokenRange& range, CSSGradientColorStop& stop, CSSParserMode cssParserMode)
+{
+ CSSValueID id = range.peek().functionId();
+ if (id != CSSValueFrom && id != CSSValueTo && id != CSSValueColorStop)
+ return false;
+
+ CSSParserTokenRange args = consumeFunction(range);
+ double position;
+ if (id == CSSValueFrom || id == CSSValueTo) {
+ position = (id == CSSValueFrom) ? 0 : 1;
+ } else {
+ ASSERT(id == CSSValueColorStop);
+ const CSSParserToken& arg = args.consumeIncludingWhitespace();
+ if (arg.type() == PercentageToken)
+ position = arg.numericValue() / 100.0;
+ else if (arg.type() == NumberToken)
+ position = arg.numericValue();
+ else
+ return false;
+
+ if (!consumeCommaIncludingWhitespace(args))
+ return false;
+ }
+
+ stop.m_position = CSSValuePool::singleton().createValue(position, CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ stop.m_color = consumeDeprecatedGradientStopColor(args, cssParserMode);
+ return stop.m_color && args.atEnd();
+}
+
+static RefPtr<CSSValue> consumeDeprecatedGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode)
+{
+ RefPtr<CSSGradientValue> result;
+ CSSValueID id = args.consumeIncludingWhitespace().id();
+ bool isDeprecatedRadialGradient = (id == CSSValueRadial);
+ if (isDeprecatedRadialGradient)
+ result = CSSRadialGradientValue::create(NonRepeating, CSSDeprecatedRadialGradient);
+ else if (id == CSSValueLinear)
+ result = CSSLinearGradientValue::create(NonRepeating, CSSDeprecatedLinearGradient);
+ if (!result || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> point = consumeDeprecatedGradientPoint(args, true);
+ if (!point)
+ return nullptr;
+ result->setFirstX(point.copyRef());
+ point = consumeDeprecatedGradientPoint(args, false);
+ if (!point)
+ return nullptr;
+ result->setFirstY(point.copyRef());
+
+ if (!consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ // For radial gradients only, we now expect a numeric radius.
+ if (isDeprecatedRadialGradient) {
+ RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
+ if (!radius || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ downcast<CSSRadialGradientValue>(result.get())->setFirstRadius(radius.copyRef());
+ }
+
+ point = consumeDeprecatedGradientPoint(args, true);
+ if (!point)
+ return nullptr;
+ result->setSecondX(point.copyRef());
+ point = consumeDeprecatedGradientPoint(args, false);
+ if (!point)
+ return nullptr;
+ result->setSecondY(point.copyRef());
+
+ // For radial gradients only, we now expect the second radius.
+ if (isDeprecatedRadialGradient) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
+ if (!radius)
+ return nullptr;
+ downcast<CSSRadialGradientValue>(result.get())->setSecondRadius(radius.copyRef());
+ }
+
+ CSSGradientColorStop stop;
+ while (consumeCommaIncludingWhitespace(args)) {
+ if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode))
+ return nullptr;
+ result->addStop(stop);
+ }
+
+ return result;
+}
+
+static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue* gradient)
+{
+ bool supportsColorHints = gradient->gradientType() == CSSLinearGradient || gradient->gradientType() == CSSRadialGradient;
+
+ // The first color stop cannot be a color hint.
+ bool previousStopWasColorHint = true;
+ do {
+ CSSGradientColorStop stop;
+ stop.m_color = consumeColor(range, cssParserMode);
+ // Two hints in a row are not allowed.
+ if (!stop.m_color && (!supportsColorHints || previousStopWasColorHint))
+ return false;
+
+ previousStopWasColorHint = !stop.m_color;
+
+ // FIXME-NEWPARSER: This boolean could be removed. Null checking color would be sufficient.
+ stop.isMidpoint = !stop.m_color;
+
+ stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
+ if (!stop.m_color && !stop.m_position)
+ return false;
+ gradient->addStop(stop);
+ } while (consumeCommaIncludingWhitespace(range));
+
+ // The last color stop cannot be a color hint.
+ if (previousStopWasColorHint)
+ return false;
+
+ // Must have 2 or more stops to be valid.
+ return gradient->stopCount() >= 2;
+}
+
+static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
+{
+ RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient);
+ RefPtr<CSSPrimitiveValue> centerX;
+ RefPtr<CSSPrimitiveValue> centerY;
+ consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
+ if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ result->setFirstX(centerX.copyRef());
+ result->setFirstY(centerY.copyRef());
+ result->setSecondX(centerX.copyRef());
+ result->setSecondY(centerY.copyRef());
+
+ RefPtr<CSSPrimitiveValue> shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
+ RefPtr<CSSPrimitiveValue> sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
+ if (!shape)
+ shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
+ result->setShape(shape.copyRef());
+ result->setSizingBehavior(sizeKeyword.copyRef());
+
+ // Or, two lengths or percentages
+ if (!shape && !sizeKeyword) {
+ RefPtr<CSSPrimitiveValue> horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ RefPtr<CSSPrimitiveValue> verticalSize;
+ if (horizontalSize) {
+ verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (!verticalSize)
+ return nullptr;
+ consumeCommaIncludingWhitespace(args);
+ result->setEndHorizontalSize(horizontalSize.copyRef());
+ result->setEndVerticalSize(verticalSize.copyRef());
+ }
+ } else {
+ consumeCommaIncludingWhitespace(args);
+ }
+ if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+ return nullptr;
+
+ return result;
+}
+
+static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
+{
+ RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSRadialGradient);
+
+ RefPtr<CSSPrimitiveValue> shape;
+ RefPtr<CSSPrimitiveValue> sizeKeyword;
+ RefPtr<CSSPrimitiveValue> horizontalSize;
+ RefPtr<CSSPrimitiveValue> verticalSize;
+
+ // First part of grammar, the size/shape clause:
+ // [ circle || <length> ] |
+ // [ ellipse || [ <length> | <percentage> ]{2} ] |
+ // [ [ circle | ellipse] || <size-keyword> ]
+ for (int i = 0; i < 3; ++i) {
+ if (args.peek().type() == IdentToken) {
+ CSSValueID id = args.peek().id();
+ if (id == CSSValueCircle || id == CSSValueEllipse) {
+ if (shape)
+ return nullptr;
+ shape = consumeIdent(args);
+ } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) {
+ if (sizeKeyword)
+ return nullptr;
+ sizeKeyword = consumeIdent(args);
+ } else {
+ break;
+ }
+ } else {
+ RefPtr<CSSPrimitiveValue> center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (!center)
+ break;
+ if (horizontalSize)
+ return nullptr;
+ horizontalSize = center;
+ center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (center) {
+ verticalSize = center;
+ ++i;
+ }
+ }
+ }
+
+ // You can specify size as a keyword or a length/percentage, not both.
+ if (sizeKeyword && horizontalSize)
+ return nullptr;
+ // Circles must have 0 or 1 lengths.
+ if (shape && shape->valueID() == CSSValueCircle && verticalSize)
+ return nullptr;
+ // Ellipses must have 0 or 2 length/percentages.
+ if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
+ return nullptr;
+ // If there's only one size, it must be a length.
+ if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
+ return nullptr;
+ if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
+ || (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
+ return nullptr;
+
+ result->setShape(shape.copyRef());
+ result->setSizingBehavior(sizeKeyword.copyRef());
+ result->setEndHorizontalSize(horizontalSize.copyRef());
+ result->setEndVerticalSize(verticalSize.copyRef());
+
+ RefPtr<CSSPrimitiveValue> centerX;
+ RefPtr<CSSPrimitiveValue> centerY;
+ if (args.peek().id() == CSSValueAt) {
+ args.consumeIncludingWhitespace();
+ consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
+ if (!(centerX && centerY))
+ return nullptr;
+
+ result->setFirstX(centerX.copyRef());
+ result->setFirstY(centerY.copyRef());
+
+ // Right now, CSS radial gradients have the same start and end centers.
+ result->setSecondX(centerX.copyRef());
+ result->setSecondY(centerY.copyRef());
+ }
+
+ if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+ return nullptr;
+ return result;
+}
+
+static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType)
+{
+ RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating, gradientType);
+
+ bool expectComma = true;
+ RefPtr<CSSPrimitiveValue> angle = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
+ if (angle)
+ result->setAngle(angle.releaseNonNull());
+ else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) {
+ RefPtr<CSSPrimitiveValue> endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
+ RefPtr<CSSPrimitiveValue> endY = consumeIdent<CSSValueBottom, CSSValueTop>(args);
+ if (!endX && !endY) {
+ if (gradientType == CSSLinearGradient)
+ return nullptr;
+ endY = CSSPrimitiveValue::createIdentifier(CSSValueTop);
+ expectComma = false;
+ } else if (!endX) {
+ endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
+ }
+
+ result->setFirstX(endX.copyRef());
+ result->setFirstY(endY.copyRef());
+ } else {
+ expectComma = false;
+ }
+
+ if (expectComma && !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+ return nullptr;
+ return result;
+}
+
+RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ return consumeImage(range, context);
+}
+
+static RefPtr<CSSValue> consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context, bool prefixed)
+{
+ RefPtr<CSSValue> fromImageValue = consumeImageOrNone(args, context);
+ if (!fromImageValue || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ RefPtr<CSSValue> toImageValue = consumeImageOrNone(args, context);
+ if (!toImageValue || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> percentage;
+ const CSSParserToken& percentageArg = args.consumeIncludingWhitespace();
+ if (percentageArg.type() == PercentageToken)
+ percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentageArg.numericValue() / 100, 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ else if (percentageArg.type() == NumberToken)
+ percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentageArg.numericValue(), 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+
+ if (!percentage)
+ return nullptr;
+ return CSSCrossfadeValue::create(fromImageValue.releaseNonNull(), toImageValue.releaseNonNull(), percentage.releaseNonNull(), prefixed);
+}
+
+static RefPtr<CSSValue> consumeWebkitCanvas(CSSParserTokenRange& args)
+{
+ if (args.peek().type() != IdentToken)
+ return nullptr;
+ auto canvasName = args.consumeIncludingWhitespace().value().toString();
+ if (!args.atEnd())
+ return nullptr;
+ return CSSCanvasValue::create(canvasName);
+}
+
+static RefPtr<CSSValue> consumeWebkitNamedImage(CSSParserTokenRange& args)
+{
+ if (args.peek().type() != IdentToken)
+ return nullptr;
+ auto imageName = args.consumeIncludingWhitespace().value().toString();
+ if (!args.atEnd())
+ return nullptr;
+ return CSSNamedImageValue::create(imageName);
+}
+
+static RefPtr<CSSValue> consumeFilterImage(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+ auto imageValue = consumeImageOrNone(args, context);
+ if (!imageValue || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ auto filterValue = consumeFilter(args, context);
+
+ if (!filterValue)
+ return nullptr;
+
+ if (!args.atEnd())
+ return nullptr;
+
+ return CSSFilterImageValue::create(imageValue.releaseNonNull(), filterValue.releaseNonNull());
+}
+
+static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context)
+{
+ CSSValueID id = range.peek().functionId();
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+ RefPtr<CSSValue> result;
+ if (id == CSSValueRadialGradient)
+ result = consumeRadialGradient(args, context.mode, NonRepeating);
+ else if (id == CSSValueRepeatingRadialGradient)
+ result = consumeRadialGradient(args, context.mode, Repeating);
+ else if (id == CSSValueWebkitLinearGradient)
+ result = consumeLinearGradient(args, context.mode, NonRepeating, CSSPrefixedLinearGradient);
+ else if (id == CSSValueWebkitRepeatingLinearGradient)
+ result = consumeLinearGradient(args, context.mode, Repeating, CSSPrefixedLinearGradient);
+ else if (id == CSSValueRepeatingLinearGradient)
+ result = consumeLinearGradient(args, context.mode, Repeating, CSSLinearGradient);
+ else if (id == CSSValueLinearGradient)
+ result = consumeLinearGradient(args, context.mode, NonRepeating, CSSLinearGradient);
+ else if (id == CSSValueWebkitGradient)
+ result = consumeDeprecatedGradient(args, context.mode);
+ else if (id == CSSValueWebkitRadialGradient)
+ result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
+ else if (id == CSSValueWebkitRepeatingRadialGradient)
+ result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
+ else if (id == CSSValueWebkitCrossFade || id == CSSValueCrossFade)
+ result = consumeCrossFade(args, context, id == CSSValueWebkitCrossFade);
+ else if (id == CSSValueWebkitCanvas)
+ result = consumeWebkitCanvas(args);
+ else if (id == CSSValueWebkitNamedImage)
+ result = consumeWebkitNamedImage(args);
+ else if (id == CSSValueWebkitFilter || id == CSSValueFilter)
+ result = consumeFilterImage(args, context);
+ if (!result || !args.atEnd())
+ return nullptr;
+ range = rangeCopy;
+ return result;
+}
+
+static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+ RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create();
+ do {
+ AtomicString urlValue = consumeUrlAsStringView(args).toAtomicString();
+ if (urlValue.isNull())
+ return nullptr;
+
+ RefPtr<CSSValue> image = CSSImageValue::create(completeURL(context, urlValue));
+ imageSet->append(image.releaseNonNull());
+
+ const CSSParserToken& token = args.consumeIncludingWhitespace();
+ if (token.type() != DimensionToken)
+ return nullptr;
+ if (token.value() != "x")
+ return nullptr;
+ ASSERT(token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN);
+ double imageScaleFactor = token.numericValue();
+ if (imageScaleFactor <= 0)
+ return nullptr;
+ imageSet->append(CSSValuePool::singleton().createValue(imageScaleFactor, CSSPrimitiveValue::UnitType::CSS_NUMBER));
+ } while (consumeCommaIncludingWhitespace(args));
+ if (!args.atEnd())
+ return nullptr;
+ range = rangeCopy;
+ return imageSet;
+}
+
+static bool isGeneratedImage(CSSValueID id)
+{
+ return id == CSSValueLinearGradient || id == CSSValueRadialGradient
+ || id == CSSValueRepeatingLinearGradient || id == CSSValueRepeatingRadialGradient
+ || id == CSSValueWebkitLinearGradient || id == CSSValueWebkitRadialGradient
+ || id == CSSValueWebkitRepeatingLinearGradient || id == CSSValueWebkitRepeatingRadialGradient
+ || id == CSSValueWebkitGradient || id == CSSValueWebkitCrossFade || id == CSSValueWebkitCanvas
+ || id == CSSValueCrossFade || id == CSSValueWebkitNamedImage || id == CSSValueWebkitFilter || id == CSSValueFilter;
+}
+
+static bool isValidPrimitiveFilterFunction(CSSValueID filterFunction)
+{
+ switch (filterFunction) {
+ case CSSValueBlur:
+ case CSSValueBrightness:
+ case CSSValueContrast:
+ case CSSValueDropShadow:
+ case CSSValueGrayscale:
+ case CSSValueHueRotate:
+ case CSSValueInvert:
+ case CSSValueOpacity:
+ case CSSValueSaturate:
+ case CSSValueSepia:
+ return true;
+ default:
+ return false;
+ }
+}
+
+RefPtr<CSSFunctionValue> consumeFilterFunction(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ CSSValueID filterType = range.peek().functionId();
+ if (!isValidPrimitiveFilterFunction(filterType))
+ return nullptr;
+ CSSParserTokenRange args = consumeFunction(range);
+ RefPtr<CSSFunctionValue> filterValue = CSSFunctionValue::create(filterType);
+ RefPtr<CSSValue> parsedValue;
+
+ if (filterType == CSSValueDropShadow)
+ parsedValue = consumeSingleShadow(args, context.mode, false, false);
+ else {
+ if (args.atEnd())
+ return filterValue;
+ if (filterType == CSSValueBrightness) {
+ parsedValue = consumePercent(args, ValueRangeAll);
+ if (!parsedValue)
+ parsedValue = consumeNumber(args, ValueRangeAll);
+ } else if (filterType == CSSValueHueRotate)
+ parsedValue = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
+ else if (filterType == CSSValueBlur)
+ parsedValue = consumeLength(args, HTMLStandardMode, ValueRangeNonNegative);
+ else {
+ parsedValue = consumePercent(args, ValueRangeNonNegative);
+ if (!parsedValue)
+ parsedValue = consumeNumber(args, ValueRangeNonNegative);
+ if (parsedValue && filterType != CSSValueSaturate && filterType != CSSValueContrast) {
+ bool isPercentage = downcast<CSSPrimitiveValue>(*parsedValue).isPercentage();
+ double maxAllowed = isPercentage ? 100.0 : 1.0;
+ if (downcast<CSSPrimitiveValue>(*parsedValue).doubleValue() > maxAllowed)
+ parsedValue = CSSPrimitiveValue::create(maxAllowed, isPercentage ? CSSPrimitiveValue::UnitType::CSS_PERCENTAGE : CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ }
+ }
+ }
+ if (!parsedValue || !args.atEnd())
+ return nullptr;
+ filterValue->append(parsedValue.releaseNonNull());
+ return filterValue;
+}
+
+RefPtr<CSSValue> consumeFilter(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ auto list = CSSValueList::createSpaceSeparated();
+ do {
+ RefPtr<CSSValue> filterValue = consumeUrl(range);
+ if (!filterValue) {
+ filterValue = consumeFilterFunction(range, context);
+ if (!filterValue)
+ return nullptr;
+ }
+ list->append(filterValue.releaseNonNull());
+ } while (!range.atEnd());
+
+ return list.ptr();
+}
+
+RefPtr<CSSShadowValue> consumeSingleShadow(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool allowInset, bool allowSpread)
+{
+ RefPtr<CSSPrimitiveValue> style;
+ RefPtr<CSSPrimitiveValue> color;
+
+ if (range.atEnd())
+ return nullptr;
+ if (range.peek().id() == CSSValueInset) {
+ if (!allowInset)
+ return nullptr;
+ style = consumeIdent(range);
+ }
+ color = consumeColor(range, cssParserMode);
+
+ auto horizontalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
+ if (!horizontalOffset)
+ return nullptr;
+
+ auto verticalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
+ if (!verticalOffset)
+ return nullptr;
+
+ auto blurRadius = consumeLength(range, cssParserMode, ValueRangeAll);
+ RefPtr<CSSPrimitiveValue> spreadDistance;
+ if (blurRadius) {
+ // Blur radius must be non-negative.
+ if (blurRadius->doubleValue() < 0)
+ return nullptr;
+ if (allowSpread)
+ spreadDistance = consumeLength(range, cssParserMode, ValueRangeAll);
+ }
+
+ if (!range.atEnd()) {
+ if (!color)
+ color = consumeColor(range, cssParserMode);
+ if (range.peek().id() == CSSValueInset) {
+ if (!allowInset || style)
+ return nullptr;
+ style = consumeIdent(range);
+ }
+ }
+
+ return CSSShadowValue::create(WTFMove(horizontalOffset), WTFMove(verticalOffset), WTFMove(blurRadius), WTFMove(spreadDistance), WTFMove(style), WTFMove(color));
+}
+
+RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage)
+{
+ AtomicString uri = consumeUrlAsStringView(range).toAtomicString();
+ if (!uri.isNull())
+ return CSSImageValue::create(completeURL(context, uri));
+ if (range.peek().type() == FunctionToken) {
+ CSSValueID id = range.peek().functionId();
+ if (id == CSSValueWebkitImageSet || id == CSSValueImageSet)
+ return consumeImageSet(range, context);
+ if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id))
+ return consumeGeneratedImage(range, context);
+ }
+ return nullptr;
+}
+
+} // namespace CSSPropertyParserHelpers
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSPropertyParserHelpers.h b/Source/WebCore/css/parser/CSSPropertyParserHelpers.h
new file mode 100644
index 000000000..754e3e5eb
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSPropertyParserHelpers.h
@@ -0,0 +1,122 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSCustomIdentValue.h"
+#include "CSSFunctionValue.h"
+#include "CSSParserMode.h"
+#include "CSSParserTokenRange.h"
+#include "CSSPrimitiveValue.h"
+#include "CSSShadowValue.h"
+#include "CSSValuePool.h"
+#include "Length.h" // For ValueRange
+
+namespace WebCore {
+
+// When these functions are successful, they will consume all the relevant
+// tokens from the range and also consume any whitespace which follows. When
+// the start of the range doesn't match the type we're looking for, the range
+// will not be modified.
+namespace CSSPropertyParserHelpers {
+
+// FIXME: These should probably just be consumeComma and consumeSlash.
+bool consumeCommaIncludingWhitespace(CSSParserTokenRange&);
+bool consumeSlashIncludingWhitespace(CSSParserTokenRange&);
+// consumeFunction expects the range starts with a FunctionToken.
+CSSParserTokenRange consumeFunction(CSSParserTokenRange&);
+
+enum class UnitlessQuirk {
+ Allow,
+ Forbid
+};
+
+RefPtr<CSSPrimitiveValue> consumeInteger(CSSParserTokenRange&, double minimumValue = -std::numeric_limits<double>::max());
+bool consumePositiveIntegerRaw(CSSParserTokenRange&, int& result);
+RefPtr<CSSPrimitiveValue> consumePositiveInteger(CSSParserTokenRange&);
+bool consumeNumberRaw(CSSParserTokenRange&, double& result);
+RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange&, ValueRange);
+RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange&, CSSParserMode, ValueRange, UnitlessQuirk = UnitlessQuirk::Forbid);
+RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange&, ValueRange);
+RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange&, CSSParserMode, ValueRange, UnitlessQuirk = UnitlessQuirk::Forbid);
+RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange&, CSSParserMode, UnitlessQuirk = UnitlessQuirk::Forbid);
+RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange&, CSSParserMode, ValueRange, UnitlessQuirk = UnitlessQuirk::Forbid);
+
+RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange&);
+RefPtr<CSSPrimitiveValue> consumeIdentRange(CSSParserTokenRange&, CSSValueID lower, CSSValueID upper);
+template<CSSValueID, CSSValueID...> inline bool identMatches(CSSValueID id);
+template<CSSValueID... allowedIdents> RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange&);
+
+RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange&);
+RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange&);
+StringView consumeUrlAsStringView(CSSParserTokenRange&);
+RefPtr<CSSPrimitiveValue> consumeUrl(CSSParserTokenRange&);
+
+RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange&, CSSParserMode, bool acceptQuirkyColors = false);
+
+RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange&, CSSParserMode, UnitlessQuirk);
+bool consumePosition(CSSParserTokenRange&, CSSParserMode, UnitlessQuirk, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY);
+bool consumeOneOrTwoValuedPosition(CSSParserTokenRange&, CSSParserMode, UnitlessQuirk, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY);
+
+enum class ConsumeGeneratedImage {
+ Allow,
+ Forbid
+};
+
+RefPtr<CSSValue> consumeImage(CSSParserTokenRange&, CSSParserContext, ConsumeGeneratedImage = ConsumeGeneratedImage::Allow);
+RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange&, CSSParserContext);
+
+RefPtr<CSSValue> consumeFilter(CSSParserTokenRange&, const CSSParserContext&);
+RefPtr<CSSFunctionValue> consumeFilterFunction(CSSParserTokenRange&, const CSSParserContext&);
+RefPtr<CSSShadowValue> consumeSingleShadow(CSSParserTokenRange&, CSSParserMode, bool allowInset, bool allowSpread);
+
+// Template implementations are at the bottom of the file for readability.
+
+template<typename... emptyBaseCase> inline bool identMatches(CSSValueID) { return false; }
+template<CSSValueID head, CSSValueID... tail> inline bool identMatches(CSSValueID id)
+{
+ return id == head || identMatches<tail...>(id);
+}
+
+// FIXME-NEWPARSER - converted to a RefPtr return type from a raw ptr.
+template<CSSValueID... names> RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != IdentToken || !identMatches<names...>(range.peek().id()))
+ return nullptr;
+ return CSSValuePool::singleton().createIdentifierValue(range.consumeIncludingWhitespace().id());
+}
+
+static inline bool isCSSWideKeyword(const CSSValueID& id)
+{
+ return id == CSSValueInitial || id == CSSValueInherit || id == CSSValueUnset || id == CSSValueRevert || id == CSSValueDefault;
+}
+
+} // namespace CSSPropertyParserHelpers
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSSelectorParser.cpp b/Source/WebCore/css/parser/CSSSelectorParser.cpp
new file mode 100644
index 000000000..08892c955
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSSelectorParser.cpp
@@ -0,0 +1,891 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSSelectorParser.h"
+
+#include "CSSParserIdioms.h"
+#include "CSSParserMode.h"
+#include "CSSSelectorList.h"
+#include "StyleSheetContents.h"
+#include <memory>
+
+namespace WebCore {
+
+CSSSelectorList CSSSelectorParser::parseSelector(CSSParserTokenRange range, const CSSParserContext& context, StyleSheetContents* styleSheet)
+{
+ CSSSelectorParser parser(context, styleSheet);
+ range.consumeWhitespace();
+ CSSSelectorList result = parser.consumeComplexSelectorList(range);
+ if (!range.atEnd())
+ return CSSSelectorList();
+ return result;
+}
+
+CSSSelectorParser::CSSSelectorParser(const CSSParserContext& context, StyleSheetContents* styleSheet)
+ : m_context(context)
+ , m_styleSheet(styleSheet)
+{
+}
+
+CSSSelectorList CSSSelectorParser::consumeComplexSelectorList(CSSParserTokenRange& range)
+{
+ Vector<std::unique_ptr<CSSParserSelector>> selectorList;
+ std::unique_ptr<CSSParserSelector> selector = consumeComplexSelector(range);
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ while (!range.atEnd() && range.peek().type() == CommaToken) {
+ range.consumeIncludingWhitespace();
+ selector = consumeComplexSelector(range);
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ }
+
+ CSSSelectorList list;
+ if (m_failedParsing)
+ return list;
+ list.adoptSelectorVector(selectorList);
+ return list;
+}
+
+CSSSelectorList CSSSelectorParser::consumeCompoundSelectorList(CSSParserTokenRange& range)
+{
+ Vector<std::unique_ptr<CSSParserSelector>> selectorList;
+ std::unique_ptr<CSSParserSelector> selector = consumeCompoundSelector(range);
+ range.consumeWhitespace();
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ while (!range.atEnd() && range.peek().type() == CommaToken) {
+ range.consumeIncludingWhitespace();
+ selector = consumeCompoundSelector(range);
+ range.consumeWhitespace();
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ }
+
+ CSSSelectorList list;
+ if (m_failedParsing)
+ return list;
+ list.adoptSelectorVector(selectorList);
+ return list;
+}
+
+static bool consumeLangArgumentList(std::unique_ptr<Vector<AtomicString>>& argumentList, CSSParserTokenRange& range)
+{
+ const CSSParserToken& ident = range.consumeIncludingWhitespace();
+ if (ident.type() != IdentToken && ident.type() != StringToken)
+ return false;
+ StringView string = ident.value();
+ if (string.startsWith("--"))
+ return false;
+ argumentList->append(string.toAtomicString());
+ while (!range.atEnd() && range.peek().type() == CommaToken) {
+ range.consumeIncludingWhitespace();
+ const CSSParserToken& ident = range.consumeIncludingWhitespace();
+ if (ident.type() != IdentToken && ident.type() != StringToken)
+ return false;
+ StringView string = ident.value();
+ if (string.startsWith("--"))
+ return false;
+ argumentList->append(string.toAtomicString());
+ }
+ return range.atEnd();
+}
+
+namespace {
+
+enum CompoundSelectorFlags {
+ HasPseudoElementForRightmostCompound = 1 << 0,
+ HasContentPseudoElement = 1 << 1
+};
+
+unsigned extractCompoundFlags(const CSSParserSelector& simpleSelector, CSSParserMode parserMode)
+{
+ if (simpleSelector.match() != CSSSelector::PseudoElement)
+ return 0;
+
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=161747
+ // The UASheetMode check is a work-around to allow this selector in mediaControls(New).css:
+ // input[type="range" i]::-webkit-media-slider-container > div {
+ if (parserMode == UASheetMode && simpleSelector.pseudoElementType() == CSSSelector::PseudoElementWebKitCustom)
+ return 0;
+ return HasPseudoElementForRightmostCompound;
+}
+
+} // namespace
+
+static bool isDescendantCombinator(CSSSelector::RelationType relation)
+{
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ return relation == CSSSelector::DescendantSpace || relation == CSSSelector::DescendantDoubleChild;
+#else
+ return relation == CSSSelector::DescendantSpace;
+#endif
+}
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeComplexSelector(CSSParserTokenRange& range)
+{
+ std::unique_ptr<CSSParserSelector> selector = consumeCompoundSelector(range);
+ if (!selector)
+ return nullptr;
+
+ unsigned previousCompoundFlags = 0;
+
+ for (CSSParserSelector* simple = selector.get(); simple && !previousCompoundFlags; simple = simple->tagHistory())
+ previousCompoundFlags |= extractCompoundFlags(*simple, m_context.mode);
+
+ while (auto combinator = consumeCombinator(range)) {
+ std::unique_ptr<CSSParserSelector> nextSelector = consumeCompoundSelector(range);
+ if (!nextSelector)
+ return isDescendantCombinator(combinator) ? WTFMove(selector) : nullptr;
+ if (previousCompoundFlags & HasPseudoElementForRightmostCompound)
+ return nullptr;
+ CSSParserSelector* end = nextSelector.get();
+ unsigned compoundFlags = extractCompoundFlags(*end, m_context.mode);
+ while (end->tagHistory()) {
+ end = end->tagHistory();
+ compoundFlags |= extractCompoundFlags(*end, m_context.mode);
+ }
+ end->setRelation(combinator);
+ previousCompoundFlags = compoundFlags;
+ end->setTagHistory(WTFMove(selector));
+
+ selector = WTFMove(nextSelector);
+ }
+
+ return selector;
+}
+
+namespace {
+
+bool isScrollbarPseudoClass(CSSSelector::PseudoClassType pseudo)
+{
+ switch (pseudo) {
+ case CSSSelector::PseudoClassEnabled:
+ case CSSSelector::PseudoClassDisabled:
+ case CSSSelector::PseudoClassHover:
+ case CSSSelector::PseudoClassActive:
+ case CSSSelector::PseudoClassHorizontal:
+ case CSSSelector::PseudoClassVertical:
+ case CSSSelector::PseudoClassDecrement:
+ case CSSSelector::PseudoClassIncrement:
+ case CSSSelector::PseudoClassStart:
+ case CSSSelector::PseudoClassEnd:
+ case CSSSelector::PseudoClassDoubleButton:
+ case CSSSelector::PseudoClassSingleButton:
+ case CSSSelector::PseudoClassNoButton:
+ case CSSSelector::PseudoClassCornerPresent:
+ case CSSSelector::PseudoClassWindowInactive:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool isUserActionPseudoClass(CSSSelector::PseudoClassType pseudo)
+{
+ switch (pseudo) {
+ case CSSSelector::PseudoClassHover:
+ case CSSSelector::PseudoClassFocus:
+ case CSSSelector::PseudoClassActive:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool isPseudoClassValidAfterPseudoElement(CSSSelector::PseudoClassType pseudoClass, CSSSelector::PseudoElementType compoundPseudoElement)
+{
+ switch (compoundPseudoElement) {
+ case CSSSelector::PseudoElementResizer:
+ case CSSSelector::PseudoElementScrollbar:
+ case CSSSelector::PseudoElementScrollbarCorner:
+ case CSSSelector::PseudoElementScrollbarButton:
+ case CSSSelector::PseudoElementScrollbarThumb:
+ case CSSSelector::PseudoElementScrollbarTrack:
+ case CSSSelector::PseudoElementScrollbarTrackPiece:
+ return isScrollbarPseudoClass(pseudoClass);
+ case CSSSelector::PseudoElementSelection:
+ return pseudoClass == CSSSelector::PseudoClassWindowInactive;
+ case CSSSelector::PseudoElementWebKitCustom:
+ case CSSSelector::PseudoElementWebKitCustomLegacyPrefixed:
+ return isUserActionPseudoClass(pseudoClass);
+ default:
+ return false;
+ }
+}
+
+bool isSimpleSelectorValidAfterPseudoElement(const CSSParserSelector& simpleSelector, CSSSelector::PseudoElementType compoundPseudoElement)
+{
+ if (compoundPseudoElement == CSSSelector::PseudoElementUnknown)
+ return true;
+ // FIXME-NEWPARSER: This doesn't exist for us.
+ // if (compoundPseudoElement == CSSSelector::PseudoElementContent)
+ // return simpleSelector.match() != CSSSelector::PseudoElement;
+ if (simpleSelector.match() != CSSSelector::PseudoClass)
+ return false;
+ CSSSelector::PseudoClassType pseudo = simpleSelector.pseudoClassType();
+ if (pseudo == CSSSelector::PseudoClassNot) {
+ ASSERT(simpleSelector.selectorList());
+ ASSERT(simpleSelector.selectorList()->first());
+ pseudo = simpleSelector.selectorList()->first()->pseudoClassType();
+ }
+ return isPseudoClassValidAfterPseudoElement(pseudo, compoundPseudoElement);
+}
+
+} // namespace
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeCompoundSelector(CSSParserTokenRange& range)
+{
+ std::unique_ptr<CSSParserSelector> compoundSelector;
+
+ AtomicString namespacePrefix;
+ AtomicString elementName;
+ CSSSelector::PseudoElementType compoundPseudoElement = CSSSelector::PseudoElementUnknown;
+ if (!consumeName(range, elementName, namespacePrefix)) {
+ compoundSelector = consumeSimpleSelector(range);
+ if (!compoundSelector)
+ return nullptr;
+ if (compoundSelector->match() == CSSSelector::PseudoElement)
+ compoundPseudoElement = compoundSelector->pseudoElementType();
+ }
+
+ while (std::unique_ptr<CSSParserSelector> simpleSelector = consumeSimpleSelector(range)) {
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=161747
+ // The UASheetMode check is a work-around to allow this selector in mediaControls(New).css:
+ // video::-webkit-media-text-track-region-container.scrolling
+ if (m_context.mode != UASheetMode && !isSimpleSelectorValidAfterPseudoElement(*simpleSelector.get(), compoundPseudoElement)) {
+ m_failedParsing = true;
+ return nullptr;
+ }
+ if (simpleSelector->match() == CSSSelector::PseudoElement)
+ compoundPseudoElement = simpleSelector->pseudoElementType();
+
+ if (compoundSelector)
+ compoundSelector = addSimpleSelectorToCompound(WTFMove(compoundSelector), WTFMove(simpleSelector));
+ else
+ compoundSelector = WTFMove(simpleSelector);
+ }
+
+ if (!compoundSelector) {
+ AtomicString namespaceURI = determineNamespace(namespacePrefix);
+ if (namespaceURI.isNull()) {
+ m_failedParsing = true;
+ return nullptr;
+ }
+ if (namespaceURI == defaultNamespace())
+ namespacePrefix = nullAtom;
+
+ CSSParserSelector* rawSelector = new CSSParserSelector(QualifiedName(namespacePrefix, elementName, namespaceURI));
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(rawSelector);
+ return selector;
+ }
+ prependTypeSelectorIfNeeded(namespacePrefix, elementName, compoundSelector.get());
+ return splitCompoundAtImplicitShadowCrossingCombinator(WTFMove(compoundSelector), m_context);
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeSimpleSelector(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.peek();
+ std::unique_ptr<CSSParserSelector> selector;
+ if (token.type() == HashToken)
+ selector = consumeId(range);
+ else if (token.type() == DelimiterToken && token.delimiter() == '.')
+ selector = consumeClass(range);
+ else if (token.type() == LeftBracketToken)
+ selector = consumeAttribute(range);
+ else if (token.type() == ColonToken)
+ selector = consumePseudo(range);
+ else
+ return nullptr;
+ if (!selector)
+ m_failedParsing = true;
+ return selector;
+}
+
+bool CSSSelectorParser::consumeName(CSSParserTokenRange& range, AtomicString& name, AtomicString& namespacePrefix)
+{
+ name = nullAtom;
+ namespacePrefix = nullAtom;
+
+ const CSSParserToken& firstToken = range.peek();
+ if (firstToken.type() == IdentToken) {
+ name = firstToken.value().toAtomicString();
+ range.consume();
+ } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '*') {
+ name = starAtom;
+ range.consume();
+ } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '|') {
+ // This is an empty namespace, which'll get assigned this value below
+ name = emptyAtom;
+ } else
+ return false;
+
+ if (range.peek().type() != DelimiterToken || range.peek().delimiter() != '|')
+ return true;
+ range.consume();
+
+ namespacePrefix = name;
+ const CSSParserToken& nameToken = range.consume();
+ if (nameToken.type() == IdentToken) {
+ name = nameToken.value().toAtomicString();
+ } else if (nameToken.type() == DelimiterToken && nameToken.delimiter() == '*')
+ name = starAtom;
+ else {
+ name = nullAtom;
+ namespacePrefix = nullAtom;
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeId(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == HashToken);
+ if (range.peek().getHashTokenType() != HashTokenId)
+ return nullptr;
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector());
+ selector->setMatch(CSSSelector::Id);
+
+ // FIXME-NEWPARSER: Avoid having to do this, but the old parser does and we need
+ // to be compatible for now.
+ CSSParserToken token = range.consume();
+ selector->setValue(token.value().toAtomicString(), m_context.mode == HTMLQuirksMode);
+ return selector;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeClass(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == DelimiterToken);
+ ASSERT(range.peek().delimiter() == '.');
+ range.consume();
+ if (range.peek().type() != IdentToken)
+ return nullptr;
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector());
+ selector->setMatch(CSSSelector::Class);
+
+ // FIXME-NEWPARSER: Avoid having to do this, but the old parser does and we need
+ // to be compatible for now.
+ CSSParserToken token = range.consume();
+ selector->setValue(token.value().toAtomicString(), m_context.mode == HTMLQuirksMode);
+
+ return selector;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeAttribute(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == LeftBracketToken);
+ CSSParserTokenRange block = range.consumeBlock();
+ if (block.end() == range.end())
+ return nullptr; // No ] was found. Be strict about this.
+
+ block.consumeWhitespace();
+
+ AtomicString namespacePrefix;
+ AtomicString attributeName;
+ if (!consumeName(block, attributeName, namespacePrefix))
+ return nullptr;
+ block.consumeWhitespace();
+
+ AtomicString namespaceURI = determineNamespace(namespacePrefix);
+ if (namespaceURI.isNull())
+ return nullptr;
+
+ QualifiedName qualifiedName = namespacePrefix.isNull()
+ ? QualifiedName(nullAtom, attributeName, nullAtom)
+ : QualifiedName(namespacePrefix, attributeName, namespaceURI);
+
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector());
+
+ if (block.atEnd()) {
+ selector->setAttribute(qualifiedName, m_context.isHTMLDocument, CSSSelector::CaseSensitive);
+ selector->setMatch(CSSSelector::Set);
+ return selector;
+ }
+
+ selector->setMatch(consumeAttributeMatch(block));
+
+ const CSSParserToken& attributeValue = block.consumeIncludingWhitespace();
+ if (attributeValue.type() != IdentToken && attributeValue.type() != StringToken)
+ return nullptr;
+ selector->setValue(attributeValue.value().toAtomicString());
+
+ selector->setAttribute(qualifiedName, m_context.isHTMLDocument, consumeAttributeFlags(block));
+
+ if (!block.atEnd())
+ return nullptr;
+ return selector;
+}
+
+static bool isOnlyPseudoClassFunction(CSSSelector::PseudoClassType pseudoClassType)
+{
+ switch (pseudoClassType) {
+ case CSSSelector::PseudoClassNot:
+ case CSSSelector::PseudoClassMatches:
+ case CSSSelector::PseudoClassNthChild:
+ case CSSSelector::PseudoClassNthLastChild:
+ case CSSSelector::PseudoClassNthOfType:
+ case CSSSelector::PseudoClassNthLastOfType:
+ case CSSSelector::PseudoClassLang:
+ case CSSSelector::PseudoClassAny:
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ case CSSSelector::PseudoClassDir:
+ case CSSSelector::PseudoClassRole:
+#endif
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static bool isOnlyPseudoElementFunction(CSSSelector::PseudoElementType pseudoElementType)
+{
+ // Note that we omit cue since it can be both an ident or a function.
+ switch (pseudoElementType) {
+ case CSSSelector::PseudoElementSlotted:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumePseudo(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == ColonToken);
+ range.consume();
+
+ int colons = 1;
+ if (range.peek().type() == ColonToken) {
+ range.consume();
+ colons++;
+ }
+
+ const CSSParserToken& token = range.peek();
+ if (token.type() != IdentToken && token.type() != FunctionToken)
+ return nullptr;
+
+ std::unique_ptr<CSSParserSelector> selector;
+
+ auto lowercasedValue = token.value().toString().convertToASCIILowercase();
+ auto value = StringView { lowercasedValue };
+
+ if (colons == 1)
+ selector = std::unique_ptr<CSSParserSelector>(CSSParserSelector::parsePseudoClassSelectorFromStringView(value));
+ else {
+ selector = std::unique_ptr<CSSParserSelector>(CSSParserSelector::parsePseudoElementSelectorFromStringView(value));
+#if ENABLE(VIDEO_TRACK)
+ // Treat the ident version of cue as PseudoElementWebkitCustom.
+ if (token.type() == IdentToken && selector && selector->match() == CSSSelector::PseudoElement && selector->pseudoElementType() == CSSSelector::PseudoElementCue)
+ selector->setPseudoElementType(CSSSelector::PseudoElementWebKitCustom);
+#endif
+ }
+
+ if (!selector || (selector->match() == CSSSelector::PseudoElement && m_disallowPseudoElements))
+ return nullptr;
+
+ if (token.type() == IdentToken) {
+ range.consume();
+ if ((selector->match() == CSSSelector::PseudoElement && (selector->pseudoElementType() == CSSSelector::PseudoElementUnknown || isOnlyPseudoElementFunction(selector->pseudoElementType())))
+ || (selector->match() == CSSSelector::PseudoClass && (selector->pseudoClassType() == CSSSelector::PseudoClassUnknown || isOnlyPseudoClassFunction(selector->pseudoClassType()))))
+ return nullptr;
+ return selector;
+ }
+
+ CSSParserTokenRange block = range.consumeBlock();
+ if (block.end() == range.end())
+ return nullptr; // No ) was found. Be strict about this.
+ block.consumeWhitespace();
+ if (token.type() != FunctionToken)
+ return nullptr;
+
+ const auto& argumentStart = block.peek();
+
+ if (selector->match() == CSSSelector::PseudoClass) {
+ switch (selector->pseudoClassType()) {
+ case CSSSelector::PseudoClassNot: {
+ DisallowPseudoElementsScope scope(this);
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeComplexSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+ case CSSSelector::PseudoClassNthChild:
+ case CSSSelector::PseudoClassNthLastChild:
+ case CSSSelector::PseudoClassNthOfType:
+ case CSSSelector::PseudoClassNthLastOfType: {
+ std::pair<int, int> ab;
+ if (!consumeANPlusB(block, ab))
+ return nullptr;
+ block.consumeWhitespace();
+ const auto& argumentEnd = block.peek();
+ auto rangeOfANPlusB = block.makeSubRange(&argumentStart, &argumentEnd);
+ auto argument = rangeOfANPlusB.serialize();
+ selector->setArgument(argument.stripWhiteSpace());
+ if (!block.atEnd()) {
+ if (block.peek().type() != IdentToken)
+ return nullptr;
+ const CSSParserToken& ident = block.consume();
+ if (!equalIgnoringASCIICase(ident.value(), "of"))
+ return nullptr;
+ if (block.peek().type() != WhitespaceToken)
+ return nullptr;
+ DisallowPseudoElementsScope scope(this);
+ block.consumeWhitespace();
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeComplexSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ }
+ selector->setNth(ab.first, ab.second);
+ return selector;
+ }
+ case CSSSelector::PseudoClassLang: {
+ // FIXME: CSS Selectors Level 4 allows :lang(*-foo)
+ auto argumentList = std::make_unique<Vector<AtomicString>>();
+ if (!consumeLangArgumentList(argumentList, block))
+ return nullptr;
+ selector->setLangArgumentList(WTFMove(argumentList));
+ return selector;
+ }
+ case CSSSelector::PseudoClassMatches: {
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeComplexSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+ case CSSSelector::PseudoClassAny:
+ case CSSSelector::PseudoClassHost: {
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeCompoundSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ case CSSSelector::PseudoClassDir:
+ case CSSSelector::PseudoClassRole: {
+ const CSSParserToken& ident = block.consumeIncludingWhitespace();
+ if (ident.type() != IdentToken || !block.atEnd())
+ return nullptr;
+ selector->setArgument(ident.value().toAtomicString());
+ return selector;
+ }
+#endif
+ default:
+ break;
+ }
+
+ }
+
+ if (selector->match() == CSSSelector::PseudoElement) {
+ switch (selector->pseudoElementType()) {
+#if ENABLE(VIDEO_TRACK)
+ case CSSSelector::PseudoElementCue: {
+ DisallowPseudoElementsScope scope(this);
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeCompoundSelectorList(block);
+ if (!selectorList->isValid() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+#endif
+ case CSSSelector::PseudoElementSlotted: {
+ DisallowPseudoElementsScope scope(this);
+
+ std::unique_ptr<CSSParserSelector> innerSelector = consumeCompoundSelector(block);
+ block.consumeWhitespace();
+ if (!innerSelector || !block.atEnd())
+ return nullptr;
+ Vector<std::unique_ptr<CSSParserSelector>> selectorVector;
+ selectorVector.append(WTFMove(innerSelector));
+ selector->adoptSelectorVector(selectorVector);
+ return selector;
+ }
+ default:
+ break;
+ }
+ }
+
+ return nullptr;
+}
+
+CSSSelector::RelationType CSSSelectorParser::consumeCombinator(CSSParserTokenRange& range)
+{
+ auto fallbackResult = CSSSelector::Subselector;
+ while (range.peek().type() == WhitespaceToken) {
+ range.consume();
+ fallbackResult = CSSSelector::DescendantSpace;
+ }
+
+ if (range.peek().type() != DelimiterToken)
+ return fallbackResult;
+
+ UChar delimiter = range.peek().delimiter();
+
+ if (delimiter == '+' || delimiter == '~' || delimiter == '>') {
+ if (delimiter == '+') {
+ range.consumeIncludingWhitespace();
+ return CSSSelector::DirectAdjacent;
+ }
+
+ if (delimiter == '~') {
+ range.consumeIncludingWhitespace();
+ return CSSSelector::IndirectAdjacent;
+ }
+
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ range.consume();
+ if (range.peek().type() == DelimiterToken && range.peek().delimiter() == '>') {
+ range.consumeIncludingWhitespace();
+ return CSSSelector::DescendantDoubleChild;
+ }
+ range.consumeWhitespace();
+#else
+ range.consumeIncludingWhitespace();
+#endif
+ return CSSSelector::Child;
+ }
+
+ return fallbackResult;
+}
+
+CSSSelector::Match CSSSelectorParser::consumeAttributeMatch(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.consumeIncludingWhitespace();
+ switch (token.type()) {
+ case IncludeMatchToken:
+ return CSSSelector::List;
+ case DashMatchToken:
+ return CSSSelector::Hyphen;
+ case PrefixMatchToken:
+ return CSSSelector::Begin;
+ case SuffixMatchToken:
+ return CSSSelector::End;
+ case SubstringMatchToken:
+ return CSSSelector::Contain;
+ case DelimiterToken:
+ if (token.delimiter() == '=')
+ return CSSSelector::Exact;
+ FALLTHROUGH;
+ default:
+ m_failedParsing = true;
+ return CSSSelector::Exact;
+ }
+}
+
+CSSSelector::AttributeMatchType CSSSelectorParser::consumeAttributeFlags(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != IdentToken)
+ return CSSSelector::CaseSensitive;
+ const CSSParserToken& flag = range.consumeIncludingWhitespace();
+ if (equalIgnoringASCIICase(flag.value(), "i"))
+ return CSSSelector::CaseInsensitive;
+ m_failedParsing = true;
+ return CSSSelector::CaseSensitive;
+}
+
+bool CSSSelectorParser::consumeANPlusB(CSSParserTokenRange& range, std::pair<int, int>& result)
+{
+ const CSSParserToken& token = range.consume();
+ if (token.type() == NumberToken && token.numericValueType() == IntegerValueType) {
+ result = std::make_pair(0, static_cast<int>(token.numericValue()));
+ return true;
+ }
+ if (token.type() == IdentToken) {
+ if (equalIgnoringASCIICase(token.value(), "odd")) {
+ result = std::make_pair(2, 1);
+ return true;
+ }
+ if (equalIgnoringASCIICase(token.value(), "even")) {
+ result = std::make_pair(2, 0);
+ return true;
+ }
+ }
+
+ // The 'n' will end up as part of an ident or dimension. For a valid <an+b>,
+ // this will store a string of the form 'n', 'n-', or 'n-123'.
+ String nString;
+
+ if (token.type() == DelimiterToken && token.delimiter() == '+' && range.peek().type() == IdentToken) {
+ result.first = 1;
+ nString = range.consume().value().toString();
+ } else if (token.type() == DimensionToken && token.numericValueType() == IntegerValueType) {
+ result.first = token.numericValue();
+ nString = token.value().toString();
+ } else if (token.type() == IdentToken) {
+ if (token.value()[0] == '-') {
+ result.first = -1;
+ nString = token.value().toString().substring(1);
+ } else {
+ result.first = 1;
+ nString = token.value().toString();
+ }
+ }
+
+ range.consumeWhitespace();
+
+ if (nString.isEmpty() || !isASCIIAlphaCaselessEqual(nString[0], 'n'))
+ return false;
+ if (nString.length() > 1 && nString[1] != '-')
+ return false;
+
+ if (nString.length() > 2) {
+ bool valid;
+ result.second = nString.substring(1).toIntStrict(&valid);
+ return valid;
+ }
+
+ NumericSign sign = nString.length() == 1 ? NoSign : MinusSign;
+ if (sign == NoSign && range.peek().type() == DelimiterToken) {
+ char delimiterSign = range.consumeIncludingWhitespace().delimiter();
+ if (delimiterSign == '+')
+ sign = PlusSign;
+ else if (delimiterSign == '-')
+ sign = MinusSign;
+ else
+ return false;
+ }
+
+ if (sign == NoSign && range.peek().type() != NumberToken) {
+ result.second = 0;
+ return true;
+ }
+
+ const CSSParserToken& b = range.consume();
+ if (b.type() != NumberToken || b.numericValueType() != IntegerValueType)
+ return false;
+ if ((b.numericSign() == NoSign) == (sign == NoSign))
+ return false;
+ result.second = b.numericValue();
+ if (sign == MinusSign)
+ result.second = -result.second;
+ return true;
+}
+
+const AtomicString& CSSSelectorParser::defaultNamespace() const
+{
+ if (!m_styleSheet)
+ return starAtom;
+ return m_styleSheet->defaultNamespace();
+}
+
+const AtomicString& CSSSelectorParser::determineNamespace(const AtomicString& prefix)
+{
+ if (prefix.isNull())
+ return defaultNamespace();
+ if (prefix.isEmpty())
+ return emptyAtom; // No namespace. If an element/attribute has a namespace, we won't match it.
+ if (prefix == starAtom)
+ return starAtom; // We'll match any namespace.
+ if (!m_styleSheet)
+ return nullAtom; // Cannot resolve prefix to namespace without a stylesheet, syntax error.
+ return m_styleSheet->namespaceURIFromPrefix(prefix);
+}
+
+void CSSSelectorParser::prependTypeSelectorIfNeeded(const AtomicString& namespacePrefix, const AtomicString& elementName, CSSParserSelector* compoundSelector)
+{
+ bool isShadowDOM = compoundSelector->needsImplicitShadowCombinatorForMatching();
+
+ if (elementName.isNull() && defaultNamespace() == starAtom && !isShadowDOM)
+ return;
+
+ AtomicString determinedElementName = elementName.isNull() ? starAtom : elementName;
+ AtomicString namespaceURI = determineNamespace(namespacePrefix);
+ if (namespaceURI.isNull()) {
+ m_failedParsing = true;
+ return;
+ }
+ AtomicString determinedPrefix = namespacePrefix;
+ if (namespaceURI == defaultNamespace())
+ determinedPrefix = nullAtom;
+ QualifiedName tag = QualifiedName(determinedPrefix, determinedElementName, namespaceURI);
+
+ // *:host never matches, so we can't discard the *,
+ // otherwise we can't tell the difference between *:host and just :host.
+ //
+ // Also, selectors where we use a ShadowPseudo combinator between the
+ // element and the pseudo element for matching (custom pseudo elements,
+ // ::cue), we need a universal selector to set the combinator
+ // (relation) on in the cases where there are no simple selectors preceding
+ // the pseudo element.
+ bool explicitForHost = compoundSelector->isHostPseudoSelector() && !elementName.isNull();
+ if (tag != anyQName() || explicitForHost || isShadowDOM)
+ compoundSelector->prependTagSelector(tag, determinedPrefix == nullAtom && determinedElementName == starAtom && !explicitForHost);
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::addSimpleSelectorToCompound(std::unique_ptr<CSSParserSelector> compoundSelector, std::unique_ptr<CSSParserSelector> simpleSelector)
+{
+ compoundSelector->appendTagHistory(CSSSelector::Subselector, WTFMove(simpleSelector));
+ return compoundSelector;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::splitCompoundAtImplicitShadowCrossingCombinator(std::unique_ptr<CSSParserSelector> compoundSelector, const CSSParserContext& context)
+{
+ // The tagHistory is a linked list that stores combinator separated compound selectors
+ // from right-to-left. Yet, within a single compound selector, stores the simple selectors
+ // from left-to-right.
+ //
+ // ".a.b > div#id" is stored in a tagHistory as [div, #id, .a, .b], each element in the
+ // list stored with an associated relation (combinator or Subselector).
+ //
+ // ::cue, ::shadow, and custom pseudo elements have an implicit ShadowPseudo combinator
+ // to their left, which really makes for a new compound selector, yet it's consumed by
+ // the selector parser as a single compound selector.
+ //
+ // Example: input#x::-webkit-clear-button -> [ ::-webkit-clear-button, input, #x ]
+ //
+ CSSParserSelector* splitAfter = compoundSelector.get();
+ while (splitAfter->tagHistory() && !splitAfter->tagHistory()->needsImplicitShadowCombinatorForMatching())
+ splitAfter = splitAfter->tagHistory();
+
+ if (!splitAfter || !splitAfter->tagHistory())
+ return compoundSelector;
+
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=161747
+ // We have to recur, since we have rules in media controls like video::a::b. This should not be allowed, and
+ // we should remove this recursion once those rules are gone.
+ std::unique_ptr<CSSParserSelector> secondCompound = context.mode != UASheetMode ? splitAfter->releaseTagHistory() : splitCompoundAtImplicitShadowCrossingCombinator(splitAfter->releaseTagHistory(), context);
+ secondCompound->appendTagHistory(CSSSelector::ShadowDescendant, WTFMove(compoundSelector));
+ return secondCompound;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSSelectorParser.h b/Source/WebCore/css/parser/CSSSelectorParser.h
new file mode 100644
index 000000000..283028737
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSSelectorParser.h
@@ -0,0 +1,108 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserSelector.h"
+#include "CSSParserTokenRange.h"
+#include <memory>
+
+namespace WebCore {
+
+struct CSSParserContext;
+class CSSSelectorList;
+class StyleSheetContents;
+
+// FIXME: We should consider building CSSSelectors directly instead of using
+// the intermediate CSSParserSelector.
+class CSSSelectorParser {
+public:
+ static CSSSelectorList parseSelector(CSSParserTokenRange, const CSSParserContext&, StyleSheetContents*);
+
+ static bool consumeANPlusB(CSSParserTokenRange&, std::pair<int, int>&);
+
+private:
+ CSSSelectorParser(const CSSParserContext&, StyleSheetContents*);
+
+ // These will all consume trailing comments if successful
+
+ CSSSelectorList consumeComplexSelectorList(CSSParserTokenRange&);
+ CSSSelectorList consumeCompoundSelectorList(CSSParserTokenRange&);
+
+ std::unique_ptr<CSSParserSelector> consumeComplexSelector(CSSParserTokenRange&);
+ std::unique_ptr<CSSParserSelector> consumeCompoundSelector(CSSParserTokenRange&);
+ // This doesn't include element names, since they're handled specially
+ std::unique_ptr<CSSParserSelector> consumeSimpleSelector(CSSParserTokenRange&);
+
+ bool consumeName(CSSParserTokenRange&, AtomicString& name, AtomicString& namespacePrefix);
+
+ // These will return nullptr when the selector is invalid
+ std::unique_ptr<CSSParserSelector> consumeId(CSSParserTokenRange&);
+ std::unique_ptr<CSSParserSelector> consumeClass(CSSParserTokenRange&);
+ std::unique_ptr<CSSParserSelector> consumePseudo(CSSParserTokenRange&);
+ std::unique_ptr<CSSParserSelector> consumeAttribute(CSSParserTokenRange&);
+
+ CSSSelector::RelationType consumeCombinator(CSSParserTokenRange&);
+ CSSSelector::Match consumeAttributeMatch(CSSParserTokenRange&);
+ CSSSelector::AttributeMatchType consumeAttributeFlags(CSSParserTokenRange&);
+
+ const AtomicString& defaultNamespace() const;
+ const AtomicString& determineNamespace(const AtomicString& prefix);
+ void prependTypeSelectorIfNeeded(const AtomicString& namespacePrefix, const AtomicString& elementName, CSSParserSelector*);
+ static std::unique_ptr<CSSParserSelector> addSimpleSelectorToCompound(std::unique_ptr<CSSParserSelector> compoundSelector, std::unique_ptr<CSSParserSelector> simpleSelector);
+ static std::unique_ptr<CSSParserSelector> splitCompoundAtImplicitShadowCrossingCombinator(std::unique_ptr<CSSParserSelector> compoundSelector, const CSSParserContext&);
+
+ const CSSParserContext& m_context;
+ RefPtr<StyleSheetContents> m_styleSheet; // FIXME: Should be const
+
+ bool m_failedParsing = false;
+ bool m_disallowPseudoElements = false;
+
+ class DisallowPseudoElementsScope {
+ WTF_MAKE_NONCOPYABLE(DisallowPseudoElementsScope);
+ public:
+ DisallowPseudoElementsScope(CSSSelectorParser* parser)
+ : m_parser(parser), m_wasDisallowed(m_parser->m_disallowPseudoElements)
+ {
+ m_parser->m_disallowPseudoElements = true;
+ }
+
+ ~DisallowPseudoElementsScope()
+ {
+ m_parser->m_disallowPseudoElements = m_wasDisallowed;
+ }
+
+ private:
+ CSSSelectorParser* m_parser;
+ bool m_wasDisallowed;
+ };
+};
+
+} // namespace WebCore
+
diff --git a/Source/WebCore/css/parser/CSSSupportsParser.cpp b/Source/WebCore/css/parser/CSSSupportsParser.cpp
new file mode 100644
index 000000000..0d71bec1c
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSSupportsParser.cpp
@@ -0,0 +1,127 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSSupportsParser.h"
+
+#include "CSSParserImpl.h"
+
+namespace WebCore {
+
+CSSSupportsParser::SupportsResult CSSSupportsParser::supportsCondition(CSSParserTokenRange range, CSSParserImpl& parser)
+{
+ // FIXME: The spec allows leading whitespace in @supports but not CSS.supports,
+ // but major browser vendors allow it in CSS.supports also.
+ range.consumeWhitespace();
+ return CSSSupportsParser(parser).consumeCondition(range);
+}
+
+enum ClauseType { Unresolved, Conjunction, Disjunction };
+
+CSSSupportsParser::SupportsResult CSSSupportsParser::consumeCondition(CSSParserTokenRange range)
+{
+ if (range.peek().type() == IdentToken || range.peek().type() == FunctionToken)
+ return consumeNegation(range);
+
+ bool result;
+ ClauseType clauseType = Unresolved;
+
+ auto previousTokenType = IdentToken;
+
+ while (true) {
+ SupportsResult nextResult = consumeConditionInParenthesis(range, previousTokenType);
+ if (nextResult == Invalid)
+ return Invalid;
+ bool nextSupported = nextResult;
+ if (clauseType == Unresolved)
+ result = nextSupported;
+ else if (clauseType == Conjunction)
+ result &= nextSupported;
+ else
+ result |= nextSupported;
+
+ if (range.atEnd())
+ break;
+ range.consumeWhitespace();
+ if (range.atEnd())
+ break;
+
+ const CSSParserToken& token = range.peek();
+ if (token.type() != IdentToken && token.type() != FunctionToken)
+ return Invalid;
+
+ previousTokenType = token.type();
+
+ if (clauseType == Unresolved)
+ clauseType = token.value().length() == 3 ? Conjunction : Disjunction;
+ if ((clauseType == Conjunction && !equalIgnoringASCIICase(token.value(), "and"))
+ || (clauseType == Disjunction && !equalIgnoringASCIICase(token.value(), "or")))
+ return Invalid;
+
+ if (token.type() == IdentToken)
+ range.consumeIncludingWhitespace();
+ }
+ return result ? Supported : Unsupported;
+}
+
+CSSSupportsParser::SupportsResult CSSSupportsParser::consumeNegation(CSSParserTokenRange range)
+{
+ ASSERT(range.peek().type() == IdentToken || range.peek().type() == FunctionToken);
+ auto tokenType = range.peek().type();
+ if (!equalIgnoringASCIICase(range.peek().value(), "not"))
+ return Invalid;
+ if (range.peek().type() == IdentToken)
+ range.consumeIncludingWhitespace();
+ SupportsResult result = consumeConditionInParenthesis(range, tokenType);
+ range.consumeWhitespace();
+ if (!range.atEnd() || result == Invalid)
+ return Invalid;
+ return result ? Unsupported : Supported;
+}
+
+CSSSupportsParser::SupportsResult CSSSupportsParser::consumeConditionInParenthesis(CSSParserTokenRange& range, CSSParserTokenType startTokenType)
+{
+ if (startTokenType == IdentToken && range.peek().type() != LeftParenthesisToken)
+ return Invalid;
+
+ CSSParserTokenRange innerRange = range.consumeBlock();
+ innerRange.consumeWhitespace();
+ SupportsResult result = consumeCondition(innerRange);
+ if (result != Invalid)
+ return result;
+
+ if (innerRange.peek().type() == FunctionToken) {
+ innerRange.consumeComponentValue();
+ return Unsupported;
+ }
+
+ return innerRange.peek().type() == IdentToken && m_parser.supportsDeclaration(innerRange) ? Supported : Unsupported;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSSupportsParser.h b/Source/WebCore/css/parser/CSSSupportsParser.h
new file mode 100644
index 000000000..ecf3c805d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSSupportsParser.h
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserToken.h"
+
+namespace WebCore {
+
+class CSSParserImpl;
+class CSSParserTokenRange;
+
+class CSSSupportsParser {
+public:
+ enum SupportsResult {
+ Unsupported = false,
+ Supported = true,
+ Invalid
+ };
+
+ static SupportsResult supportsCondition(CSSParserTokenRange, CSSParserImpl&);
+
+private:
+ CSSSupportsParser(CSSParserImpl& parser)
+ : m_parser(parser) { }
+
+ SupportsResult consumeCondition(CSSParserTokenRange);
+ SupportsResult consumeNegation(CSSParserTokenRange);
+
+ SupportsResult consumeConditionInParenthesis(CSSParserTokenRange&, CSSParserTokenType);
+
+ CSSParserImpl& m_parser;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSTokenizer.cpp b/Source/WebCore/css/parser/CSSTokenizer.cpp
new file mode 100644
index 000000000..1f1a23e4d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSTokenizer.cpp
@@ -0,0 +1,879 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSTokenizer.h"
+
+#include "CSSParserIdioms.h"
+#include "CSSParserObserverWrapper.h"
+#include "CSSParserTokenRange.h"
+#include "CSSTokenizerInputStream.h"
+#include "HTMLParserIdioms.h"
+#include <wtf/text/StringBuilder.h>
+#include <wtf/unicode/CharacterNames.h>
+
+namespace WebCore {
+
+CSSTokenizer::CSSTokenizer(const String& string)
+ : m_input(string)
+{
+ // According to the spec, we should perform preprocessing here.
+ // See: http://dev.w3.org/csswg/css-syntax/#input-preprocessing
+ //
+ // However, we can skip this step since:
+ // * We're using HTML spaces (which accept \r and \f as a valid white space)
+ // * Do not count white spaces
+ // * CSSTokenizerInputStream::nextInputChar() replaces NULLs for replacement characters
+
+ if (string.isEmpty())
+ return;
+
+ // To avoid resizing we err on the side of reserving too much space.
+ // Most strings we tokenize have about 3.5 to 5 characters per token.
+ m_tokens.reserveInitialCapacity(string.length() / 3);
+
+ while (true) {
+ CSSParserToken token = nextToken();
+ if (token.type() == CommentToken)
+ continue;
+ if (token.type() == EOFToken)
+ return;
+ m_tokens.append(token);
+ }
+}
+
+CSSTokenizer::CSSTokenizer(const String& string, CSSParserObserverWrapper& wrapper)
+ : m_input(string)
+{
+ if (string.isEmpty())
+ return;
+
+ unsigned offset = 0;
+ while (true) {
+ CSSParserToken token = nextToken();
+ if (token.type() == EOFToken)
+ break;
+ if (token.type() == CommentToken)
+ wrapper.addComment(offset, m_input.offset(), m_tokens.size());
+ else {
+ m_tokens.append(token);
+ wrapper.addToken(offset);
+ }
+ offset = m_input.offset();
+ }
+
+ wrapper.addToken(offset);
+ wrapper.finalizeConstruction(m_tokens.begin());
+}
+
+CSSParserTokenRange CSSTokenizer::tokenRange() const
+{
+ return m_tokens;
+}
+
+unsigned CSSTokenizer::tokenCount()
+{
+ return m_tokens.size();
+}
+
+static bool isNewLine(UChar cc)
+{
+ // We check \r and \f here, since we have no preprocessing stage
+ return (cc == '\r' || cc == '\n' || cc == '\f');
+}
+
+// http://dev.w3.org/csswg/css-syntax/#check-if-two-code-points-are-a-valid-escape
+static bool twoCharsAreValidEscape(UChar first, UChar second)
+{
+ return first == '\\' && !isNewLine(second);
+}
+
+void CSSTokenizer::reconsume(UChar c)
+{
+ m_input.pushBack(c);
+}
+
+UChar CSSTokenizer::consume()
+{
+ UChar current = m_input.nextInputChar();
+ m_input.advance();
+ return current;
+}
+
+CSSParserToken CSSTokenizer::whiteSpace(UChar /*cc*/)
+{
+ m_input.advanceUntilNonWhitespace();
+ return CSSParserToken(WhitespaceToken);
+}
+
+CSSParserToken CSSTokenizer::blockStart(CSSParserTokenType type)
+{
+ m_blockStack.append(type);
+ return CSSParserToken(type, CSSParserToken::BlockStart);
+}
+
+CSSParserToken CSSTokenizer::blockStart(CSSParserTokenType blockType, CSSParserTokenType type, StringView name)
+{
+ m_blockStack.append(blockType);
+ return CSSParserToken(type, name, CSSParserToken::BlockStart);
+}
+
+CSSParserToken CSSTokenizer::blockEnd(CSSParserTokenType type, CSSParserTokenType startType)
+{
+ if (!m_blockStack.isEmpty() && m_blockStack.last() == startType) {
+ m_blockStack.removeLast();
+ return CSSParserToken(type, CSSParserToken::BlockEnd);
+ }
+ return CSSParserToken(type);
+}
+
+CSSParserToken CSSTokenizer::leftParenthesis(UChar /*cc*/)
+{
+ return blockStart(LeftParenthesisToken);
+}
+
+CSSParserToken CSSTokenizer::rightParenthesis(UChar /*cc*/)
+{
+ return blockEnd(RightParenthesisToken, LeftParenthesisToken);
+}
+
+CSSParserToken CSSTokenizer::leftBracket(UChar /*cc*/)
+{
+ return blockStart(LeftBracketToken);
+}
+
+CSSParserToken CSSTokenizer::rightBracket(UChar /*cc*/)
+{
+ return blockEnd(RightBracketToken, LeftBracketToken);
+}
+
+CSSParserToken CSSTokenizer::leftBrace(UChar /*cc*/)
+{
+ return blockStart(LeftBraceToken);
+}
+
+CSSParserToken CSSTokenizer::rightBrace(UChar /*cc*/)
+{
+ return blockEnd(RightBraceToken, LeftBraceToken);
+}
+
+CSSParserToken CSSTokenizer::plusOrFullStop(UChar cc)
+{
+ if (nextCharsAreNumber(cc)) {
+ reconsume(cc);
+ return consumeNumericToken();
+ }
+ return CSSParserToken(DelimiterToken, cc);
+}
+
+CSSParserToken CSSTokenizer::asterisk(UChar cc)
+{
+ ASSERT_UNUSED(cc, cc == '*');
+ if (consumeIfNext('='))
+ return CSSParserToken(SubstringMatchToken);
+ return CSSParserToken(DelimiterToken, '*');
+}
+
+CSSParserToken CSSTokenizer::lessThan(UChar cc)
+{
+ ASSERT_UNUSED(cc, cc == '<');
+ if (m_input.peekWithoutReplacement(0) == '!'
+ && m_input.peekWithoutReplacement(1) == '-'
+ && m_input.peekWithoutReplacement(2) == '-') {
+ m_input.advance(3);
+ return CSSParserToken(CDOToken);
+ }
+ return CSSParserToken(DelimiterToken, '<');
+}
+
+CSSParserToken CSSTokenizer::comma(UChar /*cc*/)
+{
+ return CSSParserToken(CommaToken);
+}
+
+CSSParserToken CSSTokenizer::hyphenMinus(UChar cc)
+{
+ if (nextCharsAreNumber(cc)) {
+ reconsume(cc);
+ return consumeNumericToken();
+ }
+ if (m_input.peekWithoutReplacement(0) == '-'
+ && m_input.peekWithoutReplacement(1) == '>') {
+ m_input.advance(2);
+ return CSSParserToken(CDCToken);
+ }
+ if (nextCharsAreIdentifier(cc)) {
+ reconsume(cc);
+ return consumeIdentLikeToken();
+ }
+ return CSSParserToken(DelimiterToken, cc);
+}
+
+CSSParserToken CSSTokenizer::solidus(UChar cc)
+{
+ if (consumeIfNext('*')) {
+ // These get ignored, but we need a value to return.
+ consumeUntilCommentEndFound();
+ return CSSParserToken(CommentToken);
+ }
+
+ return CSSParserToken(DelimiterToken, cc);
+}
+
+CSSParserToken CSSTokenizer::colon(UChar /*cc*/)
+{
+ return CSSParserToken(ColonToken);
+}
+
+CSSParserToken CSSTokenizer::semiColon(UChar /*cc*/)
+{
+ return CSSParserToken(SemicolonToken);
+}
+
+CSSParserToken CSSTokenizer::hash(UChar cc)
+{
+ UChar nextChar = m_input.peekWithoutReplacement(0);
+ if (isNameCodePoint(nextChar) || twoCharsAreValidEscape(nextChar, m_input.peekWithoutReplacement(1))) {
+ HashTokenType type = nextCharsAreIdentifier() ? HashTokenId : HashTokenUnrestricted;
+ return CSSParserToken(type, consumeName());
+ }
+
+ return CSSParserToken(DelimiterToken, cc);
+}
+
+CSSParserToken CSSTokenizer::circumflexAccent(UChar cc)
+{
+ ASSERT_UNUSED(cc, cc == '^');
+ if (consumeIfNext('='))
+ return CSSParserToken(PrefixMatchToken);
+ return CSSParserToken(DelimiterToken, '^');
+}
+
+CSSParserToken CSSTokenizer::dollarSign(UChar cc)
+{
+ ASSERT_UNUSED(cc, cc == '$');
+ if (consumeIfNext('='))
+ return CSSParserToken(SuffixMatchToken);
+ return CSSParserToken(DelimiterToken, '$');
+}
+
+CSSParserToken CSSTokenizer::verticalLine(UChar cc)
+{
+ ASSERT_UNUSED(cc, cc == '|');
+ if (consumeIfNext('='))
+ return CSSParserToken(DashMatchToken);
+ if (consumeIfNext('|'))
+ return CSSParserToken(ColumnToken);
+ return CSSParserToken(DelimiterToken, '|');
+}
+
+CSSParserToken CSSTokenizer::tilde(UChar cc)
+{
+ ASSERT_UNUSED(cc, cc == '~');
+ if (consumeIfNext('='))
+ return CSSParserToken(IncludeMatchToken);
+ return CSSParserToken(DelimiterToken, '~');
+}
+
+CSSParserToken CSSTokenizer::commercialAt(UChar cc)
+{
+ ASSERT_UNUSED(cc, cc == '@');
+ if (nextCharsAreIdentifier())
+ return CSSParserToken(AtKeywordToken, consumeName());
+ return CSSParserToken(DelimiterToken, '@');
+}
+
+CSSParserToken CSSTokenizer::reverseSolidus(UChar cc)
+{
+ if (twoCharsAreValidEscape(cc, m_input.peekWithoutReplacement(0))) {
+ reconsume(cc);
+ return consumeIdentLikeToken();
+ }
+ return CSSParserToken(DelimiterToken, cc);
+}
+
+CSSParserToken CSSTokenizer::asciiDigit(UChar cc)
+{
+ reconsume(cc);
+ return consumeNumericToken();
+}
+
+CSSParserToken CSSTokenizer::letterU(UChar cc)
+{
+ if (m_input.peekWithoutReplacement(0) == '+'
+ && (isASCIIHexDigit(m_input.peekWithoutReplacement(1))
+ || m_input.peekWithoutReplacement(1) == '?')) {
+ m_input.advance();
+ return consumeUnicodeRange();
+ }
+ reconsume(cc);
+ return consumeIdentLikeToken();
+}
+
+CSSParserToken CSSTokenizer::nameStart(UChar cc)
+{
+ reconsume(cc);
+ return consumeIdentLikeToken();
+}
+
+CSSParserToken CSSTokenizer::stringStart(UChar cc)
+{
+ return consumeStringTokenUntil(cc);
+}
+
+CSSParserToken CSSTokenizer::endOfFile(UChar /*cc*/)
+{
+ return CSSParserToken(EOFToken);
+}
+
+const CSSTokenizer::CodePoint CSSTokenizer::codePoints[128] = {
+ &CSSTokenizer::endOfFile,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ &CSSTokenizer::whiteSpace,
+ &CSSTokenizer::whiteSpace,
+ 0,
+ &CSSTokenizer::whiteSpace,
+ &CSSTokenizer::whiteSpace,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ &CSSTokenizer::whiteSpace,
+ 0,
+ &CSSTokenizer::stringStart,
+ &CSSTokenizer::hash,
+ &CSSTokenizer::dollarSign,
+ 0,
+ 0,
+ &CSSTokenizer::stringStart,
+ &CSSTokenizer::leftParenthesis,
+ &CSSTokenizer::rightParenthesis,
+ &CSSTokenizer::asterisk,
+ &CSSTokenizer::plusOrFullStop,
+ &CSSTokenizer::comma,
+ &CSSTokenizer::hyphenMinus,
+ &CSSTokenizer::plusOrFullStop,
+ &CSSTokenizer::solidus,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::asciiDigit,
+ &CSSTokenizer::colon,
+ &CSSTokenizer::semiColon,
+ &CSSTokenizer::lessThan,
+ 0,
+ 0,
+ 0,
+ &CSSTokenizer::commercialAt,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::letterU,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::leftBracket,
+ &CSSTokenizer::reverseSolidus,
+ &CSSTokenizer::rightBracket,
+ &CSSTokenizer::circumflexAccent,
+ &CSSTokenizer::nameStart,
+ 0,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::letterU,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::nameStart,
+ &CSSTokenizer::leftBrace,
+ &CSSTokenizer::verticalLine,
+ &CSSTokenizer::rightBrace,
+ &CSSTokenizer::tilde,
+ 0,
+};
+#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
+const unsigned codePointsNumber = 128;
+#endif
+
+CSSParserToken CSSTokenizer::nextToken()
+{
+ // Unlike the HTMLTokenizer, the CSS Syntax spec is written
+ // as a stateless, (fixed-size) look-ahead tokenizer.
+ // We could move to the stateful model and instead create
+ // states for all the "next 3 codepoints are X" cases.
+ // State-machine tokenizers are easier to write to handle
+ // incremental tokenization of partial sources.
+ // However, for now we follow the spec exactly.
+ UChar cc = consume();
+ CodePoint codePointFunc = 0;
+
+ if (isASCII(cc)) {
+ ASSERT_WITH_SECURITY_IMPLICATION(cc < codePointsNumber);
+ codePointFunc = codePoints[cc];
+ } else
+ codePointFunc = &CSSTokenizer::nameStart;
+
+ if (codePointFunc)
+ return ((this)->*(codePointFunc))(cc);
+ return CSSParserToken(DelimiterToken, cc);
+}
+
+// This method merges the following spec sections for efficiency
+// http://www.w3.org/TR/css3-syntax/#consume-a-number
+// http://www.w3.org/TR/css3-syntax/#convert-a-string-to-a-number
+CSSParserToken CSSTokenizer::consumeNumber()
+{
+ ASSERT(nextCharsAreNumber());
+
+ NumericValueType type = IntegerValueType;
+ NumericSign sign = NoSign;
+ unsigned numberLength = 0;
+
+ UChar next = m_input.peekWithoutReplacement(0);
+ if (next == '+') {
+ ++numberLength;
+ sign = PlusSign;
+ } else if (next == '-') {
+ ++numberLength;
+ sign = MinusSign;
+ }
+
+ numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength);
+ next = m_input.peekWithoutReplacement(numberLength);
+ if (next == '.' && isASCIIDigit(m_input.peekWithoutReplacement(numberLength + 1))) {
+ type = NumberValueType;
+ numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength + 2);
+ next = m_input.peekWithoutReplacement(numberLength);
+ }
+
+ if (next == 'E' || next == 'e') {
+ next = m_input.peekWithoutReplacement(numberLength + 1);
+ if (isASCIIDigit(next)) {
+ type = NumberValueType;
+ numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength + 1);
+ } else if ((next == '+' || next == '-') && isASCIIDigit(m_input.peekWithoutReplacement(numberLength + 2))) {
+ type = NumberValueType;
+ numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength + 3);
+ }
+ }
+
+ double value = m_input.getDouble(0, numberLength);
+ m_input.advance(numberLength);
+
+ return CSSParserToken(NumberToken, value, type, sign);
+}
+
+// http://www.w3.org/TR/css3-syntax/#consume-a-numeric-token
+CSSParserToken CSSTokenizer::consumeNumericToken()
+{
+ CSSParserToken token = consumeNumber();
+ if (nextCharsAreIdentifier())
+ token.convertToDimensionWithUnit(consumeName());
+ else if (consumeIfNext('%'))
+ token.convertToPercentage();
+ return token;
+}
+
+// http://dev.w3.org/csswg/css-syntax/#consume-ident-like-token
+CSSParserToken CSSTokenizer::consumeIdentLikeToken()
+{
+ StringView name = consumeName();
+ if (consumeIfNext('(')) {
+ if (equalIgnoringASCIICase(name, "url")) {
+ // The spec is slightly different so as to avoid dropping whitespace
+ // tokens, but they wouldn't be used and this is easier.
+ m_input.advanceUntilNonWhitespace();
+ UChar next = m_input.peekWithoutReplacement(0);
+ if (next != '"' && next != '\'')
+ return consumeUrlToken();
+ }
+ return blockStart(LeftParenthesisToken, FunctionToken, name);
+ }
+ return CSSParserToken(IdentToken, name);
+}
+
+// http://dev.w3.org/csswg/css-syntax/#consume-a-string-token
+CSSParserToken CSSTokenizer::consumeStringTokenUntil(UChar endingCodePoint)
+{
+ // Strings without escapes get handled without allocations
+ for (unsigned size = 0; ; size++) {
+ UChar cc = m_input.peekWithoutReplacement(size);
+ if (cc == endingCodePoint) {
+ unsigned startOffset = m_input.offset();
+ m_input.advance(size + 1);
+ return CSSParserToken(StringToken, m_input.rangeAt(startOffset, size));
+ }
+ if (isNewLine(cc)) {
+ m_input.advance(size);
+ return CSSParserToken(BadStringToken);
+ }
+ if (cc == '\0' || cc == '\\')
+ break;
+ }
+
+ StringBuilder output;
+ while (true) {
+ UChar cc = consume();
+ if (cc == endingCodePoint || cc == kEndOfFileMarker)
+ return CSSParserToken(StringToken, registerString(output.toString()));
+ if (isNewLine(cc)) {
+ reconsume(cc);
+ return CSSParserToken(BadStringToken);
+ }
+ if (cc == '\\') {
+ if (m_input.nextInputChar() == kEndOfFileMarker)
+ continue;
+ if (isNewLine(m_input.peekWithoutReplacement(0)))
+ consumeSingleWhitespaceIfNext(); // This handles \r\n for us
+ else
+ output.append(consumeEscape());
+ } else
+ output.append(cc);
+ }
+}
+
+CSSParserToken CSSTokenizer::consumeUnicodeRange()
+{
+ ASSERT(isASCIIHexDigit(m_input.peekWithoutReplacement(0)) || m_input.peekWithoutReplacement(0) == '?');
+ int lengthRemaining = 6;
+ UChar32 start = 0;
+
+ while (lengthRemaining && isASCIIHexDigit(m_input.peekWithoutReplacement(0))) {
+ start = start * 16 + toASCIIHexValue(consume());
+ --lengthRemaining;
+ }
+
+ UChar32 end = start;
+ if (lengthRemaining && consumeIfNext('?')) {
+ do {
+ start *= 16;
+ end = end * 16 + 0xF;
+ --lengthRemaining;
+ } while (lengthRemaining && consumeIfNext('?'));
+ } else if (m_input.peekWithoutReplacement(0) == '-' && isASCIIHexDigit(m_input.peekWithoutReplacement(1))) {
+ m_input.advance();
+ lengthRemaining = 6;
+ end = 0;
+ do {
+ end = end * 16 + toASCIIHexValue(consume());
+ --lengthRemaining;
+ } while (lengthRemaining && isASCIIHexDigit(m_input.peekWithoutReplacement(0)));
+ }
+
+ return CSSParserToken(UnicodeRangeToken, start, end);
+}
+
+// http://dev.w3.org/csswg/css-syntax/#non-printable-code-point
+static bool isNonPrintableCodePoint(UChar cc)
+{
+ return cc <= '\x8' || cc == '\xb' || (cc >= '\xe' && cc <= '\x1f') || cc == '\x7f';
+}
+
+// http://dev.w3.org/csswg/css-syntax/#consume-url-token
+CSSParserToken CSSTokenizer::consumeUrlToken()
+{
+ m_input.advanceUntilNonWhitespace();
+
+ // URL tokens without escapes get handled without allocations
+ for (unsigned size = 0; ; size++) {
+ UChar cc = m_input.peekWithoutReplacement(size);
+ if (cc == ')') {
+ unsigned startOffset = m_input.offset();
+ m_input.advance(size + 1);
+ return CSSParserToken(UrlToken, m_input.rangeAt(startOffset, size));
+ }
+ if (cc <= ' ' || cc == '\\' || cc == '"' || cc == '\'' || cc == '(' || cc == '\x7f')
+ break;
+ }
+
+ StringBuilder result;
+ while (true) {
+ UChar cc = consume();
+ if (cc == ')' || cc == kEndOfFileMarker)
+ return CSSParserToken(UrlToken, registerString(result.toString()));
+
+ if (isHTMLSpace(cc)) {
+ m_input.advanceUntilNonWhitespace();
+ if (consumeIfNext(')') || m_input.nextInputChar() == kEndOfFileMarker)
+ return CSSParserToken(UrlToken, registerString(result.toString()));
+ break;
+ }
+
+ if (cc == '"' || cc == '\'' || cc == '(' || isNonPrintableCodePoint(cc))
+ break;
+
+ if (cc == '\\') {
+ if (twoCharsAreValidEscape(cc, m_input.peekWithoutReplacement(0))) {
+ result.append(consumeEscape());
+ continue;
+ }
+ break;
+ }
+
+ result.append(cc);
+ }
+
+ consumeBadUrlRemnants();
+ return CSSParserToken(BadUrlToken);
+}
+
+// http://dev.w3.org/csswg/css-syntax/#consume-the-remnants-of-a-bad-url
+void CSSTokenizer::consumeBadUrlRemnants()
+{
+ while (true) {
+ UChar cc = consume();
+ if (cc == ')' || cc == kEndOfFileMarker)
+ return;
+ if (twoCharsAreValidEscape(cc, m_input.peekWithoutReplacement(0)))
+ consumeEscape();
+ }
+}
+
+void CSSTokenizer::consumeSingleWhitespaceIfNext()
+{
+ // We check for \r\n and HTML spaces since we don't do preprocessing
+ UChar next = m_input.peekWithoutReplacement(0);
+ if (next == '\r' && m_input.peekWithoutReplacement(1) == '\n')
+ m_input.advance(2);
+ else if (isHTMLSpace(next))
+ m_input.advance();
+}
+
+void CSSTokenizer::consumeUntilCommentEndFound()
+{
+ UChar c = consume();
+ while (true) {
+ if (c == kEndOfFileMarker)
+ return;
+ if (c != '*') {
+ c = consume();
+ continue;
+ }
+ c = consume();
+ if (c == '/')
+ return;
+ }
+}
+
+bool CSSTokenizer::consumeIfNext(UChar character)
+{
+ // Since we're not doing replacement we can't tell the difference from
+ // a NUL in the middle and the kEndOfFileMarker, so character must not be
+ // NUL.
+ ASSERT(character);
+ if (m_input.peekWithoutReplacement(0) == character) {
+ m_input.advance();
+ return true;
+ }
+ return false;
+}
+
+// http://www.w3.org/TR/css3-syntax/#consume-a-name
+StringView CSSTokenizer::consumeName()
+{
+ // Names without escapes get handled without allocations
+ for (unsigned size = 0; ; ++size) {
+ UChar cc = m_input.peekWithoutReplacement(size);
+ if (isNameCodePoint(cc))
+ continue;
+ // peekWithoutReplacement will return NUL when we hit the end of the
+ // input. In that case we want to still use the rangeAt() fast path
+ // below.
+ if (cc == '\0' && m_input.offset() + size < m_input.length())
+ break;
+ if (cc == '\\')
+ break;
+ unsigned startOffset = m_input.offset();
+ m_input.advance(size);
+ return m_input.rangeAt(startOffset, size);
+ }
+
+ StringBuilder result;
+ while (true) {
+ UChar cc = consume();
+ if (isNameCodePoint(cc)) {
+ result.append(cc);
+ continue;
+ }
+ if (twoCharsAreValidEscape(cc, m_input.peekWithoutReplacement(0))) {
+ result.append(consumeEscape());
+ continue;
+ }
+ reconsume(cc);
+ return registerString(result.toString());
+ }
+}
+
+// http://dev.w3.org/csswg/css-syntax/#consume-an-escaped-code-point
+UChar32 CSSTokenizer::consumeEscape()
+{
+ UChar cc = consume();
+ ASSERT(!isNewLine(cc));
+ if (isASCIIHexDigit(cc)) {
+ unsigned consumedHexDigits = 1;
+ StringBuilder hexChars;
+ hexChars.append(cc);
+ while (consumedHexDigits < 6 && isASCIIHexDigit(m_input.peekWithoutReplacement(0))) {
+ cc = consume();
+ hexChars.append(cc);
+ consumedHexDigits++;
+ };
+ consumeSingleWhitespaceIfNext();
+ bool ok = false;
+ UChar32 codePoint = hexChars.toString().toUIntStrict(&ok, 16);
+ ASSERT(ok);
+ if (!codePoint || (0xD800 <= codePoint && codePoint <= 0xDFFF) || codePoint > 0x10FFFF)
+ return replacementCharacter;
+ return codePoint;
+ }
+
+ if (cc == kEndOfFileMarker)
+ return replacementCharacter;
+ return cc;
+}
+
+bool CSSTokenizer::nextTwoCharsAreValidEscape()
+{
+ return twoCharsAreValidEscape(m_input.peekWithoutReplacement(0), m_input.peekWithoutReplacement(1));
+}
+
+// http://www.w3.org/TR/css3-syntax/#starts-with-a-number
+bool CSSTokenizer::nextCharsAreNumber(UChar first)
+{
+ UChar second = m_input.peekWithoutReplacement(0);
+ if (isASCIIDigit(first))
+ return true;
+ if (first == '+' || first == '-')
+ return ((isASCIIDigit(second)) || (second == '.' && isASCIIDigit(m_input.peekWithoutReplacement(1))));
+ if (first =='.')
+ return (isASCIIDigit(second));
+ return false;
+}
+
+bool CSSTokenizer::nextCharsAreNumber()
+{
+ UChar first = consume();
+ bool areNumber = nextCharsAreNumber(first);
+ reconsume(first);
+ return areNumber;
+}
+
+// http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier
+bool CSSTokenizer::nextCharsAreIdentifier(UChar first)
+{
+ UChar second = m_input.peekWithoutReplacement(0);
+ if (isNameStartCodePoint(first) || twoCharsAreValidEscape(first, second))
+ return true;
+
+ if (first == '-')
+ return isNameStartCodePoint(second) || second == '-' || nextTwoCharsAreValidEscape();
+
+ return false;
+}
+
+bool CSSTokenizer::nextCharsAreIdentifier()
+{
+ UChar first = consume();
+ bool areIdentifier = nextCharsAreIdentifier(first);
+ reconsume(first);
+ return areIdentifier;
+}
+
+StringView CSSTokenizer::registerString(const String& string)
+{
+ m_stringPool.append(string);
+ return string;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSTokenizer.h b/Source/WebCore/css/parser/CSSTokenizer.h
new file mode 100644
index 000000000..a83ff978d
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSTokenizer.h
@@ -0,0 +1,128 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserToken.h"
+#include "CSSTokenizerInputStream.h"
+#include <climits>
+#include <wtf/text/StringView.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CSSTokenizerInputStream;
+class CSSParserObserverWrapper;
+class CSSParserTokenRange;
+
+class CSSTokenizer {
+ WTF_MAKE_NONCOPYABLE(CSSTokenizer);
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ CSSTokenizer(const String&);
+ CSSTokenizer(const String&, CSSParserObserverWrapper&); // For the inspector
+
+ CSSParserTokenRange tokenRange() const;
+ unsigned tokenCount();
+
+ Vector<String>&& escapedStringsForAdoption() { return WTFMove(m_stringPool); }
+
+private:
+ CSSParserToken nextToken();
+
+ UChar consume();
+ void reconsume(UChar);
+
+ CSSParserToken consumeNumericToken();
+ CSSParserToken consumeIdentLikeToken();
+ CSSParserToken consumeNumber();
+ CSSParserToken consumeStringTokenUntil(UChar);
+ CSSParserToken consumeUnicodeRange();
+ CSSParserToken consumeUrlToken();
+
+ void consumeBadUrlRemnants();
+ void consumeSingleWhitespaceIfNext();
+ void consumeUntilCommentEndFound();
+
+ bool consumeIfNext(UChar);
+ StringView consumeName();
+ UChar32 consumeEscape();
+
+ bool nextTwoCharsAreValidEscape();
+ bool nextCharsAreNumber(UChar);
+ bool nextCharsAreNumber();
+ bool nextCharsAreIdentifier(UChar);
+ bool nextCharsAreIdentifier();
+
+ CSSParserToken blockStart(CSSParserTokenType);
+ CSSParserToken blockStart(CSSParserTokenType blockType, CSSParserTokenType, StringView);
+ CSSParserToken blockEnd(CSSParserTokenType, CSSParserTokenType startType);
+
+ CSSParserToken whiteSpace(UChar);
+ CSSParserToken leftParenthesis(UChar);
+ CSSParserToken rightParenthesis(UChar);
+ CSSParserToken leftBracket(UChar);
+ CSSParserToken rightBracket(UChar);
+ CSSParserToken leftBrace(UChar);
+ CSSParserToken rightBrace(UChar);
+ CSSParserToken plusOrFullStop(UChar);
+ CSSParserToken comma(UChar);
+ CSSParserToken hyphenMinus(UChar);
+ CSSParserToken asterisk(UChar);
+ CSSParserToken lessThan(UChar);
+ CSSParserToken solidus(UChar);
+ CSSParserToken colon(UChar);
+ CSSParserToken semiColon(UChar);
+ CSSParserToken hash(UChar);
+ CSSParserToken circumflexAccent(UChar);
+ CSSParserToken dollarSign(UChar);
+ CSSParserToken verticalLine(UChar);
+ CSSParserToken tilde(UChar);
+ CSSParserToken commercialAt(UChar);
+ CSSParserToken reverseSolidus(UChar);
+ CSSParserToken asciiDigit(UChar);
+ CSSParserToken letterU(UChar);
+ CSSParserToken nameStart(UChar);
+ CSSParserToken stringStart(UChar);
+ CSSParserToken endOfFile(UChar);
+
+ StringView registerString(const String&);
+
+ using CodePoint = CSSParserToken (CSSTokenizer::*)(UChar);
+ static const CodePoint codePoints[];
+
+ Vector<CSSParserTokenType, 8> m_blockStack;
+ CSSTokenizerInputStream m_input;
+
+ Vector<CSSParserToken, 32> m_tokens;
+ // We only allocate strings when escapes are used.
+ Vector<String> m_stringPool;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSTokenizerInputStream.cpp b/Source/WebCore/css/parser/CSSTokenizerInputStream.cpp
new file mode 100644
index 000000000..620e6b0f3
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSTokenizerInputStream.cpp
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSTokenizerInputStream.h"
+
+#include "HTMLParserIdioms.h"
+
+namespace WebCore {
+
+CSSTokenizerInputStream::CSSTokenizerInputStream(const String& input)
+ : m_offset(0)
+ , m_stringLength(input.length())
+ , m_string(input.impl())
+{
+}
+
+void CSSTokenizerInputStream::advanceUntilNonWhitespace()
+{
+ // Using HTML space here rather than CSS space since we don't do preprocessing
+ if (m_string->is8Bit()) {
+ const LChar* characters = m_string->characters8();
+ while (m_offset < m_stringLength && isHTMLSpace(characters[m_offset]))
+ ++m_offset;
+ } else {
+ const UChar* characters = m_string->characters16();
+ while (m_offset < m_stringLength && isHTMLSpace(characters[m_offset]))
+ ++m_offset;
+ }
+}
+
+double CSSTokenizerInputStream::getDouble(unsigned start, unsigned end) const
+{
+ ASSERT(start <= end && ((m_offset + end) <= m_stringLength));
+ bool isResultOK = false;
+ double result = 0.0;
+ if (start < end) {
+ if (m_string->is8Bit())
+ result = charactersToDouble(m_string->characters8() + m_offset + start, end - start, &isResultOK);
+ else
+ result = charactersToDouble(m_string->characters16() + m_offset + start, end - start, &isResultOK);
+ }
+ // FIXME: It looks like callers ensure we have a valid number
+ return isResultOK ? result : 0.0;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSTokenizerInputStream.h b/Source/WebCore/css/parser/CSSTokenizerInputStream.h
new file mode 100644
index 000000000..96e6e69d0
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSTokenizerInputStream.h
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <wtf/text/StringView.h>
+
+namespace WebCore {
+
+constexpr LChar kEndOfFileMarker = 0;
+
+class CSSTokenizerInputStream {
+ WTF_MAKE_NONCOPYABLE(CSSTokenizerInputStream);
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ explicit CSSTokenizerInputStream(const String& input);
+
+ // Gets the char in the stream replacing NUL characters with a unicode
+ // replacement character. Will return (NUL) kEndOfFileMarker when at the
+ // end of the stream.
+ UChar nextInputChar() const
+ {
+ if (m_offset >= m_stringLength)
+ return '\0';
+ UChar result = (*m_string)[m_offset];
+ return result ? result : 0xFFFD;
+ }
+
+ // Gets the char at lookaheadOffset from the current stream position. Will
+ // return NUL (kEndOfFileMarker) if the stream position is at the end.
+ // NOTE: This may *also* return NUL if there's one in the input! Never
+ // compare the return value to '\0'.
+ UChar peekWithoutReplacement(unsigned lookaheadOffset) const
+ {
+ if ((m_offset + lookaheadOffset) >= m_stringLength)
+ return '\0';
+ return (*m_string)[m_offset + lookaheadOffset];
+ }
+
+ void advance(unsigned offset = 1) { m_offset += offset; }
+ void pushBack(UChar cc)
+ {
+ --m_offset;
+ ASSERT_UNUSED(cc, nextInputChar() == cc);
+ }
+
+ double getDouble(unsigned start, unsigned end) const;
+
+ template<bool characterPredicate(UChar)>
+ unsigned skipWhilePredicate(unsigned offset)
+ {
+ if (m_string->is8Bit()) {
+ const LChar* characters8 = m_string->characters8();
+ while ((m_offset + offset) < m_stringLength && characterPredicate(characters8[m_offset + offset]))
+ ++offset;
+ } else {
+ const UChar* characters16 = m_string->characters16();
+ while ((m_offset + offset) < m_stringLength && characterPredicate(characters16[m_offset + offset]))
+ ++offset;
+ }
+ return offset;
+ }
+
+ void advanceUntilNonWhitespace();
+
+ unsigned length() const { return m_stringLength; }
+ unsigned offset() const { return std::min(m_offset, m_stringLength); }
+
+ StringView rangeAt(unsigned start, unsigned length) const
+ {
+ ASSERT(start + length <= m_stringLength);
+ return StringView(m_string.get()).substring(start, length); // FIXME: Should make a constructor on StringView for this.
+ }
+
+private:
+ size_t m_offset;
+ const size_t m_stringLength;
+ RefPtr<StringImpl> m_string;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSVariableParser.cpp b/Source/WebCore/css/parser/CSSVariableParser.cpp
new file mode 100644
index 000000000..6acda4f83
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSVariableParser.cpp
@@ -0,0 +1,166 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSVariableParser.h"
+
+#include "CSSCustomPropertyValue.h"
+#include "CSSParserTokenRange.h"
+
+namespace WebCore {
+
+bool CSSVariableParser::isValidVariableName(const CSSParserToken& token)
+{
+ if (token.type() != IdentToken)
+ return false;
+
+ StringView value = token.value();
+ return value.length() >= 2 && value[0] == '-' && value[1] == '-';
+}
+
+bool CSSVariableParser::isValidVariableName(const String& string)
+{
+ return string.length() >= 2 && string[0] == '-' && string[1] == '-';
+}
+
+bool isValidVariableReference(CSSParserTokenRange, bool& hasAtApplyRule);
+
+static bool classifyBlock(CSSParserTokenRange range, bool& hasReferences, bool& hasAtApplyRule, bool isTopLevelBlock = true)
+{
+ while (!range.atEnd()) {
+ if (range.peek().getBlockType() == CSSParserToken::BlockStart) {
+ const CSSParserToken& token = range.peek();
+ CSSParserTokenRange block = range.consumeBlock();
+ if (token.functionId() == CSSValueVar) {
+ if (!isValidVariableReference(block, hasAtApplyRule))
+ return false; // Bail if any references are invalid
+ hasReferences = true;
+ continue;
+ }
+ if (!classifyBlock(block, hasReferences, hasAtApplyRule, false))
+ return false;
+ continue;
+ }
+
+ ASSERT(range.peek().getBlockType() != CSSParserToken::BlockEnd);
+
+ const CSSParserToken& token = range.consume();
+ switch (token.type()) {
+ case AtKeywordToken: {
+ if (equalIgnoringASCIICase(token.value(), "apply")) {
+ range.consumeWhitespace();
+ const CSSParserToken& variableName = range.consumeIncludingWhitespace();
+ if (!CSSVariableParser::isValidVariableName(variableName)
+ || !(range.atEnd() || range.peek().type() == SemicolonToken || range.peek().type() == RightBraceToken))
+ return false;
+ hasAtApplyRule = true;
+ }
+ break;
+ }
+ case DelimiterToken: {
+ if (token.delimiter() == '!' && isTopLevelBlock)
+ return false;
+ break;
+ }
+ case RightParenthesisToken:
+ case RightBraceToken:
+ case RightBracketToken:
+ case BadStringToken:
+ case BadUrlToken:
+ return false;
+ case SemicolonToken:
+ if (isTopLevelBlock)
+ return false;
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+bool isValidVariableReference(CSSParserTokenRange range, bool& hasAtApplyRule)
+{
+ range.consumeWhitespace();
+ if (!CSSVariableParser::isValidVariableName(range.consumeIncludingWhitespace()))
+ return false;
+ if (range.atEnd())
+ return true;
+
+ if (range.consume().type() != CommaToken)
+ return false;
+ if (range.atEnd())
+ return false;
+
+ bool hasReferences = false;
+ return classifyBlock(range, hasReferences, hasAtApplyRule);
+}
+
+static CSSValueID classifyVariableRange(CSSParserTokenRange range, bool& hasReferences, bool& hasAtApplyRule)
+{
+ hasReferences = false;
+ hasAtApplyRule = false;
+
+ range.consumeWhitespace();
+ if (range.peek().type() == IdentToken) {
+ CSSValueID id = range.consumeIncludingWhitespace().id();
+ if (range.atEnd() && (id == CSSValueInherit || id == CSSValueInitial || id == CSSValueUnset || id == CSSValueRevert))
+ return id;
+ }
+
+ if (classifyBlock(range, hasReferences, hasAtApplyRule))
+ return CSSValueInternalVariableValue;
+ return CSSValueInvalid;
+}
+
+bool CSSVariableParser::containsValidVariableReferences(CSSParserTokenRange range)
+{
+ bool hasReferences;
+ bool hasAtApplyRule;
+ CSSValueID type = classifyVariableRange(range, hasReferences, hasAtApplyRule);
+ return type == CSSValueInternalVariableValue && hasReferences && !hasAtApplyRule;
+}
+
+RefPtr<CSSCustomPropertyValue> CSSVariableParser::parseDeclarationValue(const AtomicString& variableName, CSSParserTokenRange range)
+{
+ if (range.atEnd())
+ return nullptr;
+
+ bool hasReferences;
+ bool hasAtApplyRule;
+ CSSValueID type = classifyVariableRange(range, hasReferences, hasAtApplyRule);
+
+ if (type == CSSValueInvalid)
+ return nullptr;
+ if (type == CSSValueInternalVariableValue)
+ return CSSCustomPropertyValue::createWithVariableData(variableName, CSSVariableData::create(range, hasReferences || hasAtApplyRule));
+ return CSSCustomPropertyValue::createWithID(variableName, type);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/CSSVariableParser.h b/Source/WebCore/css/parser/CSSVariableParser.h
new file mode 100644
index 000000000..238ebd67e
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSVariableParser.h
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserTokenRange.h"
+#include <wtf/RefPtr.h>
+#include <wtf/text/AtomicString.h>
+
+namespace WebCore {
+
+class CSSCustomPropertyValue;
+
+class CSSVariableParser {
+public:
+ static bool containsValidVariableReferences(CSSParserTokenRange);
+
+ static RefPtr<CSSCustomPropertyValue> parseDeclarationValue(const AtomicString&, CSSParserTokenRange);
+
+ static bool isValidVariableName(const CSSParserToken&);
+ static bool isValidVariableName(const String&);
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/MediaQueryBlockWatcher.cpp b/Source/WebCore/css/parser/MediaQueryBlockWatcher.cpp
new file mode 100644
index 000000000..97333a661
--- /dev/null
+++ b/Source/WebCore/css/parser/MediaQueryBlockWatcher.cpp
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "MediaQueryBlockWatcher.h"
+
+#include "CSSParserToken.h"
+
+namespace WebCore {
+
+MediaQueryBlockWatcher::MediaQueryBlockWatcher()
+{
+}
+
+void MediaQueryBlockWatcher::handleToken(const CSSParserToken& token)
+{
+ if (token.getBlockType() == CSSParserToken::BlockStart)
+ ++m_blockLevel;
+ else if (token.getBlockType() == CSSParserToken::BlockEnd) {
+ ASSERT(m_blockLevel);
+ --m_blockLevel;
+ }
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/MediaQueryBlockWatcher.h b/Source/WebCore/css/parser/MediaQueryBlockWatcher.h
new file mode 100644
index 000000000..1e6c2e1a5
--- /dev/null
+++ b/Source/WebCore/css/parser/MediaQueryBlockWatcher.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+namespace WebCore {
+
+class CSSParserToken;
+
+class MediaQueryBlockWatcher {
+public:
+ MediaQueryBlockWatcher();
+ void handleToken(const CSSParserToken&);
+ unsigned blockLevel() const { return m_blockLevel; }
+
+private:
+ unsigned m_blockLevel { 0 };
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/MediaQueryParser.cpp b/Source/WebCore/css/parser/MediaQueryParser.cpp
new file mode 100644
index 000000000..a4c9afe98
--- /dev/null
+++ b/Source/WebCore/css/parser/MediaQueryParser.cpp
@@ -0,0 +1,310 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "MediaQueryParser.h"
+
+#include "CSSParserIdioms.h"
+#include "CSSTokenizer.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(const String& queryString)
+{
+ return parseMediaQuerySet(CSSTokenizer(queryString).tokenRange());
+}
+
+RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(CSSParserTokenRange range)
+{
+ return MediaQueryParser(MediaQuerySetParser).parseInternal(range);
+}
+
+RefPtr<MediaQuerySet> MediaQueryParser::parseMediaCondition(CSSParserTokenRange range)
+{
+ return MediaQueryParser(MediaConditionParser).parseInternal(range);
+}
+
+const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor;
+const MediaQueryParser::State MediaQueryParser::ReadMediaNot = &MediaQueryParser::readMediaNot;
+const MediaQueryParser::State MediaQueryParser::ReadMediaType = &MediaQueryParser::readMediaType;
+const MediaQueryParser::State MediaQueryParser::ReadAnd = &MediaQueryParser::readAnd;
+const MediaQueryParser::State MediaQueryParser::ReadFeatureStart = &MediaQueryParser::readFeatureStart;
+const MediaQueryParser::State MediaQueryParser::ReadFeature = &MediaQueryParser::readFeature;
+const MediaQueryParser::State MediaQueryParser::ReadFeatureColon = &MediaQueryParser::readFeatureColon;
+const MediaQueryParser::State MediaQueryParser::ReadFeatureValue = &MediaQueryParser::readFeatureValue;
+const MediaQueryParser::State MediaQueryParser::ReadFeatureEnd = &MediaQueryParser::readFeatureEnd;
+const MediaQueryParser::State MediaQueryParser::SkipUntilComma = &MediaQueryParser::skipUntilComma;
+const MediaQueryParser::State MediaQueryParser::SkipUntilBlockEnd = &MediaQueryParser::skipUntilBlockEnd;
+const MediaQueryParser::State MediaQueryParser::Done = &MediaQueryParser::done;
+
+MediaQueryParser::MediaQueryParser(ParserType parserType)
+ : m_parserType(parserType)
+ , m_querySet(MediaQuerySet::create())
+{
+ if (parserType == MediaQuerySetParser)
+ m_state = &MediaQueryParser::readRestrictor;
+ else // MediaConditionParser
+ m_state = &MediaQueryParser::readMediaNot;
+}
+
+MediaQueryParser::~MediaQueryParser() { }
+
+void MediaQueryParser::setStateAndRestrict(State state, MediaQuery::Restrictor restrictor)
+{
+ m_mediaQueryData.setRestrictor(restrictor);
+ m_state = state;
+}
+
+// State machine member functions start here
+void MediaQueryParser::readRestrictor(CSSParserTokenType type, const CSSParserToken& token)
+{
+ readMediaType(type, token);
+}
+
+void MediaQueryParser::readMediaNot(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == IdentToken && equalIgnoringASCIICase(token.value(), "not"))
+ setStateAndRestrict(ReadFeatureStart, MediaQuery::Not);
+ else
+ readFeatureStart(type, token);
+}
+
+static bool isRestrictorOrLogicalOperator(const CSSParserToken& token)
+{
+ // FIXME: it would be more efficient to use lower-case always for tokenValue.
+ return equalIgnoringASCIICase(token.value(), "not")
+ || equalIgnoringASCIICase(token.value(), "and")
+ || equalIgnoringASCIICase(token.value(), "or")
+ || equalIgnoringASCIICase(token.value(), "only");
+}
+
+void MediaQueryParser::readMediaType(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == LeftParenthesisToken) {
+ if (m_mediaQueryData.restrictor() != MediaQuery::None)
+ m_state = SkipUntilComma;
+ else
+ m_state = ReadFeature;
+ } else if (type == IdentToken) {
+ if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "not"))
+ setStateAndRestrict(ReadMediaType, MediaQuery::Not);
+ else if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "only"))
+ setStateAndRestrict(ReadMediaType, MediaQuery::Only);
+ else if (m_mediaQueryData.restrictor() != MediaQuery::None
+ && isRestrictorOrLogicalOperator(token)) {
+ m_state = SkipUntilComma;
+ } else {
+ m_mediaQueryData.setMediaType(token.value().toString());
+ m_state = ReadAnd;
+ }
+ } else if (type == EOFToken && (!m_querySet->queryVector().size() || m_state != ReadRestrictor))
+ m_state = Done;
+ else {
+ m_state = SkipUntilComma;
+ if (type == CommaToken)
+ skipUntilComma(type, token);
+ }
+}
+
+void MediaQueryParser::commitMediaQuery()
+{
+ // FIXME-NEWPARSER: Convoluted and awful, but we can't change the MediaQuerySet yet because of the
+ // old parser.
+ MediaQuery mediaQuery = MediaQuery(m_mediaQueryData.restrictor(), m_mediaQueryData.mediaType(), WTFMove(m_mediaQueryData.expressions()));
+ m_mediaQueryData.clear();
+ m_querySet->addMediaQuery(WTFMove(mediaQuery));
+}
+
+void MediaQueryParser::readAnd(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == IdentToken && equalIgnoringASCIICase(token.value(), "and")) {
+ m_state = ReadFeatureStart;
+ } else if (type == CommaToken && m_parserType != MediaConditionParser) {
+ commitMediaQuery();
+ m_state = ReadRestrictor;
+ } else if (type == EOFToken)
+ m_state = Done;
+ else
+ m_state = SkipUntilComma;
+}
+
+void MediaQueryParser::readFeatureStart(CSSParserTokenType type, const CSSParserToken& /*token*/)
+{
+ if (type == LeftParenthesisToken)
+ m_state = ReadFeature;
+ else
+ m_state = SkipUntilComma;
+}
+
+void MediaQueryParser::readFeature(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == IdentToken) {
+ m_mediaQueryData.setMediaFeature(token.value().toString());
+ m_state = ReadFeatureColon;
+ } else
+ m_state = SkipUntilComma;
+}
+
+void MediaQueryParser::readFeatureColon(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == ColonToken)
+ m_state = ReadFeatureValue;
+ else if (type == RightParenthesisToken || type == EOFToken)
+ readFeatureEnd(type, token);
+ else
+ m_state = SkipUntilBlockEnd;
+}
+
+void MediaQueryParser::readFeatureValue(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == DimensionToken && token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN)
+ m_state = SkipUntilComma;
+ else {
+ if (m_mediaQueryData.tryAddParserToken(type, token))
+ m_state = ReadFeatureEnd;
+ else
+ m_state = SkipUntilBlockEnd;
+ }
+}
+
+void MediaQueryParser::readFeatureEnd(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == RightParenthesisToken || type == EOFToken) {
+ if (type != EOFToken && m_mediaQueryData.addExpression())
+ m_state = ReadAnd;
+ else
+ m_state = SkipUntilComma;
+ } else if (type == DelimiterToken && token.delimiter() == '/') {
+ m_mediaQueryData.tryAddParserToken(type, token);
+ m_state = ReadFeatureValue;
+ } else
+ m_state = SkipUntilBlockEnd;
+}
+
+void MediaQueryParser::skipUntilComma(CSSParserTokenType type, const CSSParserToken& /*token*/)
+{
+ if ((type == CommaToken && !m_blockWatcher.blockLevel()) || type == EOFToken) {
+ m_state = ReadRestrictor;
+ m_mediaQueryData.clear();
+ MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
+ m_querySet->addMediaQuery(WTFMove(query));
+ }
+}
+
+void MediaQueryParser::skipUntilBlockEnd(CSSParserTokenType /*type */, const CSSParserToken& token)
+{
+ if (token.getBlockType() == CSSParserToken::BlockEnd && !m_blockWatcher.blockLevel())
+ m_state = SkipUntilComma;
+}
+
+void MediaQueryParser::done(CSSParserTokenType /*type*/, const CSSParserToken& /*token*/) { }
+
+void MediaQueryParser::handleBlocks(const CSSParserToken& token)
+{
+ if (token.getBlockType() == CSSParserToken::BlockStart
+ && (token.type() != LeftParenthesisToken || m_blockWatcher.blockLevel()))
+ m_state = SkipUntilBlockEnd;
+}
+
+void MediaQueryParser::processToken(const CSSParserToken& token)
+{
+ CSSParserTokenType type = token.type();
+
+ handleBlocks(token);
+ m_blockWatcher.handleToken(token);
+
+ // Call the function that handles current state
+ if (type != WhitespaceToken)
+ ((this)->*(m_state))(type, token);
+}
+
+// The state machine loop
+RefPtr<MediaQuerySet> MediaQueryParser::parseInternal(CSSParserTokenRange range)
+{
+ while (!range.atEnd())
+ processToken(range.consume());
+
+ // FIXME: Can we get rid of this special case?
+ if (m_parserType == MediaQuerySetParser)
+ processToken(CSSParserToken(EOFToken));
+
+ if (m_state != ReadAnd && m_state != ReadRestrictor && m_state != Done && m_state != ReadMediaNot) {
+ MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
+ m_querySet->addMediaQuery(WTFMove(query));
+ } else if (m_mediaQueryData.currentMediaQueryChanged())
+ commitMediaQuery();
+
+ return m_querySet;
+}
+
+MediaQueryData::MediaQueryData()
+ : m_restrictor(MediaQuery::None)
+ , m_mediaType("all")
+ , m_mediaTypeSet(false)
+{
+}
+
+void MediaQueryData::clear()
+{
+ m_restrictor = MediaQuery::None;
+ m_mediaType = "all";
+ m_mediaTypeSet = false;
+ m_mediaFeature = String();
+ m_valueList.clear();
+ m_expressions.clear();
+}
+
+bool MediaQueryData::addExpression()
+{
+ MediaQueryExpression expression = MediaQueryExpression(m_mediaFeature, m_valueList);
+ bool isValid = expression.isValid();
+ m_expressions.append(WTFMove(expression));
+ m_valueList.clear();
+ return isValid;
+}
+
+bool MediaQueryData::tryAddParserToken(CSSParserTokenType type, const CSSParserToken& token)
+{
+ if (type == NumberToken || type == PercentageToken || type == DimensionToken
+ || type == DelimiterToken || type == IdentToken) {
+ m_valueList.append(token);
+ return true;
+ }
+
+ return false;
+}
+
+void MediaQueryData::setMediaType(const String& mediaType)
+{
+ m_mediaType = mediaType;
+ m_mediaTypeSet = true;
+}
+
+} // namespace WebCsore
diff --git a/Source/WebCore/css/parser/MediaQueryParser.h b/Source/WebCore/css/parser/MediaQueryParser.h
new file mode 100644
index 000000000..e51d6c32a
--- /dev/null
+++ b/Source/WebCore/css/parser/MediaQueryParser.h
@@ -0,0 +1,136 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserToken.h"
+#include "CSSParserTokenRange.h"
+#include "MediaList.h"
+#include "MediaQuery.h"
+#include "MediaQueryBlockWatcher.h"
+#include "MediaQueryExpression.h"
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class MediaQuerySet;
+
+class MediaQueryData {
+ WTF_MAKE_NONCOPYABLE(MediaQueryData);
+public:
+ MediaQueryData();
+ void clear();
+ bool addExpression();
+ bool tryAddParserToken(CSSParserTokenType, const CSSParserToken&);
+ void setMediaType(const String&);
+
+ MediaQuery::Restrictor restrictor() const { return m_restrictor; };
+ Vector<MediaQueryExpression>& expressions() { return m_expressions; }
+ String mediaType() const { return m_mediaType; }
+
+ inline bool currentMediaQueryChanged() const
+ {
+ return (m_restrictor != MediaQuery::None || m_mediaTypeSet || m_expressions.size() > 0);
+ }
+ inline MediaQuery::Restrictor restrictor() { return m_restrictor; }
+
+ inline void setRestrictor(MediaQuery::Restrictor restrictor) { m_restrictor = restrictor; }
+
+ inline void setMediaFeature(const String& str) { m_mediaFeature = str; }
+
+private:
+ MediaQuery::Restrictor m_restrictor;
+ String m_mediaType;
+ Vector<MediaQueryExpression> m_expressions;
+ String m_mediaFeature;
+ Vector<CSSParserToken, 4> m_valueList;
+ bool m_mediaTypeSet;
+};
+
+class MediaQueryParser {
+ WTF_MAKE_NONCOPYABLE(MediaQueryParser);
+public:
+ static RefPtr<MediaQuerySet> parseMediaQuerySet(const String&);
+ static RefPtr<MediaQuerySet> parseMediaQuerySet(CSSParserTokenRange);
+ static RefPtr<MediaQuerySet> parseMediaCondition(CSSParserTokenRange);
+
+private:
+ enum ParserType {
+ MediaQuerySetParser,
+ MediaConditionParser,
+ };
+
+ MediaQueryParser(ParserType);
+ virtual ~MediaQueryParser();
+
+ RefPtr<MediaQuerySet> parseInternal(CSSParserTokenRange);
+
+ void processToken(const CSSParserToken&);
+
+ void readRestrictor(CSSParserTokenType, const CSSParserToken&);
+ void readMediaNot(CSSParserTokenType, const CSSParserToken&);
+ void readMediaType(CSSParserTokenType, const CSSParserToken&);
+ void readAnd(CSSParserTokenType, const CSSParserToken&);
+ void readFeatureStart(CSSParserTokenType, const CSSParserToken&);
+ void readFeature(CSSParserTokenType, const CSSParserToken&);
+ void readFeatureColon(CSSParserTokenType, const CSSParserToken&);
+ void readFeatureValue(CSSParserTokenType, const CSSParserToken&);
+ void readFeatureEnd(CSSParserTokenType, const CSSParserToken&);
+ void skipUntilComma(CSSParserTokenType, const CSSParserToken&);
+ void skipUntilBlockEnd(CSSParserTokenType, const CSSParserToken&);
+ void done(CSSParserTokenType, const CSSParserToken&);
+
+ using State = void (MediaQueryParser::*)(CSSParserTokenType, const CSSParserToken&);
+
+ void setStateAndRestrict(State, MediaQuery::Restrictor);
+ void handleBlocks(const CSSParserToken&);
+
+ void commitMediaQuery();
+
+ State m_state;
+ ParserType m_parserType;
+ MediaQueryData m_mediaQueryData;
+ RefPtr<MediaQuerySet> m_querySet;
+ MediaQueryBlockWatcher m_blockWatcher;
+
+ const static State ReadRestrictor;
+ const static State ReadMediaNot;
+ const static State ReadMediaType;
+ const static State ReadAnd;
+ const static State ReadFeatureStart;
+ const static State ReadFeature;
+ const static State ReadFeatureColon;
+ const static State ReadFeatureValue;
+ const static State ReadFeatureEnd;
+ const static State SkipUntilComma;
+ const static State SkipUntilBlockEnd;
+ const static State Done;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/SizesAttributeParser.cpp b/Source/WebCore/css/parser/SizesAttributeParser.cpp
new file mode 100644
index 000000000..7b7969bba
--- /dev/null
+++ b/Source/WebCore/css/parser/SizesAttributeParser.cpp
@@ -0,0 +1,166 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "SizesAttributeParser.h"
+
+#include "CSSPrimitiveValue.h"
+#include "CSSToLengthConversionData.h"
+#include "CSSTokenizer.h"
+#include "MediaQueryEvaluator.h"
+#include "RenderView.h"
+#include "SizesCalcParser.h"
+#include "StyleScope.h"
+
+namespace WebCore {
+
+float SizesAttributeParser::computeLength(double value, CSSPrimitiveValue::UnitType type, const Document& document)
+{
+ auto* renderer = document.renderView();
+ if (!renderer)
+ return 0;
+ auto& style = renderer->style();
+
+ CSSToLengthConversionData conversionData(&style, &style, renderer);
+
+ // Because we evaluate "sizes" at parse time (before style has been resolved), the font metrics used for these specific units
+ // are not available. The font selector's internal consistency isn't guaranteed just yet, so we can just temporarily clear
+ // the pointer to it for the duration of the unit evaluation. This is acceptible because the style always comes from the
+ // RenderView, which has its font information hardcoded in resolveForDocument() to be -webkit-standard, whose operations
+ // don't require a font selector.
+ if (type == CSSPrimitiveValue::CSS_EXS || type == CSSPrimitiveValue::CSS_CHS) {
+ RefPtr<FontSelector> fontSelector = style.fontCascade().fontSelector();
+ style.fontCascade().update(nullptr);
+ float result = CSSPrimitiveValue::computeNonCalcLengthDouble(conversionData, type, value);
+ style.fontCascade().update(fontSelector.get());
+ return result;
+ }
+
+ return clampTo<float>(CSSPrimitiveValue::computeNonCalcLengthDouble(conversionData, type, value));
+}
+
+SizesAttributeParser::SizesAttributeParser(const String& attribute, const Document& document)
+ : m_document(document)
+ , m_length(0)
+ , m_lengthWasSet(false)
+{
+ // Ensure iframes have correct view size.
+ if (m_document.ownerElement())
+ m_document.ownerElement()->document().updateLayoutIgnorePendingStylesheets();
+
+ m_isValid = parse(CSSTokenizer(attribute).tokenRange());
+}
+
+float SizesAttributeParser::length()
+{
+ if (m_isValid)
+ return effectiveSize();
+ return effectiveSizeDefaultValue();
+}
+
+bool SizesAttributeParser::calculateLengthInPixels(CSSParserTokenRange range, float& result)
+{
+ const CSSParserToken& startToken = range.peek();
+ CSSParserTokenType type = startToken.type();
+ if (type == DimensionToken) {
+ if (!CSSPrimitiveValue::isLength(startToken.unitType()))
+ return false;
+ result = computeLength(startToken.numericValue(), startToken.unitType(), m_document);
+ if (result >= 0)
+ return true;
+ } else if (type == FunctionToken) {
+ SizesCalcParser calcParser(range, m_document);
+ if (!calcParser.isValid())
+ return false;
+ result = calcParser.result();
+ return true;
+ } else if (type == NumberToken && !startToken.numericValue()) {
+ result = 0;
+ return true;
+ }
+
+ return false;
+}
+
+bool SizesAttributeParser::mediaConditionMatches(const MediaQuerySet& mediaCondition)
+{
+ // A Media Condition cannot have a media type other than screen.
+ auto* renderer = m_document.renderView();
+ if (!renderer)
+ return false;
+ auto& style = renderer->style();
+ return MediaQueryEvaluator { "screen", m_document, &style }.evaluate(mediaCondition, const_cast<Style::Scope&>(m_document.styleScope()).resolverIfExists());
+}
+
+bool SizesAttributeParser::parse(CSSParserTokenRange range)
+{
+ // Split on a comma token and parse the result tokens as (media-condition, length) pairs
+ while (!range.atEnd()) {
+ const CSSParserToken* mediaConditionStart = &range.peek();
+ // The length is the last component value before the comma which isn't whitespace or a comment
+ const CSSParserToken* lengthTokenStart = &range.peek();
+ const CSSParserToken* lengthTokenEnd = &range.peek();
+ while (!range.atEnd() && range.peek().type() != CommaToken) {
+ lengthTokenStart = &range.peek();
+ range.consumeComponentValue();
+ lengthTokenEnd = &range.peek();
+ range.consumeWhitespace();
+ }
+ range.consume();
+
+ float length;
+ if (!calculateLengthInPixels(range.makeSubRange(lengthTokenStart, lengthTokenEnd), length))
+ continue;
+ RefPtr<MediaQuerySet> mediaCondition = MediaQueryParser::parseMediaCondition(range.makeSubRange(mediaConditionStart, lengthTokenStart));
+ if (!mediaCondition || !mediaConditionMatches(*mediaCondition))
+ continue;
+ m_length = length;
+ m_lengthWasSet = true;
+ return true;
+ }
+ return false;
+}
+
+float SizesAttributeParser::effectiveSize()
+{
+ if (m_lengthWasSet)
+ return m_length;
+ return effectiveSizeDefaultValue();
+}
+
+unsigned SizesAttributeParser::effectiveSizeDefaultValue()
+{
+ auto* renderer = m_document.renderView();
+ if (!renderer)
+ return 0;
+ auto& style = renderer->style();
+ return clampTo<float>(CSSPrimitiveValue::computeNonCalcLengthDouble({ &style, &style, renderer }, CSSPrimitiveValue::CSS_VW, 100.0));
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/SizesAttributeParser.h b/Source/WebCore/css/parser/SizesAttributeParser.h
new file mode 100644
index 000000000..38395cf8c
--- /dev/null
+++ b/Source/WebCore/css/parser/SizesAttributeParser.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "MediaQueryBlockWatcher.h"
+#include "MediaQueryParser.h"
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CSSValue;
+class Document;
+
+class SizesAttributeParser {
+public:
+ SizesAttributeParser(const String&, const Document&);
+
+ float length();
+
+ static float defaultLength(const Document&);
+ static float computeLength(double value, CSSPrimitiveValue::UnitType, const Document&);
+
+private:
+ bool parse(CSSParserTokenRange);
+ float effectiveSize();
+ bool calculateLengthInPixels(CSSParserTokenRange, float& result);
+ bool mediaConditionMatches(const MediaQuerySet& mediaCondition);
+ unsigned effectiveSizeDefaultValue();
+
+ const Document& m_document;
+ RefPtr<MediaQuerySet> m_mediaCondition;
+ float m_length;
+ bool m_lengthWasSet;
+ bool m_isValid;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/SizesCalcParser.cpp b/Source/WebCore/css/parser/SizesCalcParser.cpp
new file mode 100644
index 000000000..4c36c740a
--- /dev/null
+++ b/Source/WebCore/css/parser/SizesCalcParser.cpp
@@ -0,0 +1,259 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "SizesCalcParser.h"
+
+#include "CSSParserToken.h"
+#include "RenderView.h"
+#include "SizesAttributeParser.h"
+
+namespace WebCore {
+
+SizesCalcParser::SizesCalcParser(CSSParserTokenRange range, const Document& document)
+ : m_result(0)
+ , m_document(document)
+{
+ m_isValid = calcToReversePolishNotation(range) && calculate();
+}
+
+float SizesCalcParser::result() const
+{
+ ASSERT(m_isValid);
+ return m_result;
+}
+
+static bool operatorPriority(UChar cc, bool& highPriority)
+{
+ if (cc == '+' || cc == '-')
+ highPriority = false;
+ else if (cc == '*' || cc == '/')
+ highPriority = true;
+ else
+ return false;
+ return true;
+}
+
+bool SizesCalcParser::handleOperator(Vector<CSSParserToken>& stack, const CSSParserToken& token)
+{
+ // If the token is an operator, o1, then:
+ // while there is an operator token, o2, at the top of the stack, and
+ // either o1 is left-associative and its precedence is equal to that of o2,
+ // or o1 has precedence less than that of o2,
+ // pop o2 off the stack, onto the output queue;
+ // push o1 onto the stack.
+ bool stackOperatorPriority;
+ bool incomingOperatorPriority;
+
+ if (!operatorPriority(token.delimiter(), incomingOperatorPriority))
+ return false;
+ if (!stack.isEmpty() && stack.last().type() == DelimiterToken) {
+ if (!operatorPriority(stack.last().delimiter(), stackOperatorPriority))
+ return false;
+ if (!incomingOperatorPriority || stackOperatorPriority) {
+ appendOperator(stack.last());
+ stack.removeLast();
+ }
+ }
+ stack.append(token);
+ return true;
+}
+
+void SizesCalcParser::appendNumber(const CSSParserToken& token)
+{
+ SizesCalcValue value;
+ value.value = token.numericValue();
+ m_valueList.append(value);
+}
+
+bool SizesCalcParser::appendLength(const CSSParserToken& token)
+{
+ SizesCalcValue value;
+ double result = SizesAttributeParser::computeLength(token.numericValue(), token.unitType(), m_document);
+ value.value = result;
+ value.isLength = true;
+ m_valueList.append(value);
+ return true;
+}
+
+void SizesCalcParser::appendOperator(const CSSParserToken& token)
+{
+ SizesCalcValue value;
+ value.operation = token.delimiter();
+ m_valueList.append(value);
+}
+
+bool SizesCalcParser::calcToReversePolishNotation(CSSParserTokenRange range)
+{
+ // This method implements the shunting yard algorithm, to turn the calc syntax into a reverse polish notation.
+ // http://en.wikipedia.org/wiki/Shunting-yard_algorithm
+
+ Vector<CSSParserToken> stack;
+ while (!range.atEnd()) {
+ const CSSParserToken& token = range.consume();
+ switch (token.type()) {
+ case NumberToken:
+ appendNumber(token);
+ break;
+ case DimensionToken:
+ if (!CSSPrimitiveValue::isLength(token.unitType()) || !appendLength(token))
+ return false;
+ break;
+ case DelimiterToken:
+ if (!handleOperator(stack, token))
+ return false;
+ break;
+ case FunctionToken:
+ if (!equalIgnoringASCIICase(token.value(), "calc"))
+ return false;
+ // "calc(" is the same as "("
+ FALLTHROUGH;
+ case LeftParenthesisToken:
+ // If the token is a left parenthesis, then push it onto the stack.
+ stack.append(token);
+ break;
+ case RightParenthesisToken:
+ // If the token is a right parenthesis:
+ // Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
+ while (!stack.isEmpty() && stack.last().type() != LeftParenthesisToken && stack.last().type() != FunctionToken) {
+ appendOperator(stack.last());
+ stack.removeLast();
+ }
+ // If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
+ if (stack.isEmpty())
+ return false;
+ // Pop the left parenthesis from the stack, but not onto the output queue.
+ stack.removeLast();
+ break;
+ case WhitespaceToken:
+ case EOFToken:
+ break;
+ case CDOToken:
+ case CDCToken:
+ case AtKeywordToken:
+ case HashToken:
+ case UrlToken:
+ case BadUrlToken:
+ case PercentageToken:
+ case IncludeMatchToken:
+ case DashMatchToken:
+ case PrefixMatchToken:
+ case SuffixMatchToken:
+ case SubstringMatchToken:
+ case ColumnToken:
+ case UnicodeRangeToken:
+ case IdentToken:
+ case CommaToken:
+ case ColonToken:
+ case SemicolonToken:
+ case LeftBraceToken:
+ case LeftBracketToken:
+ case RightBraceToken:
+ case RightBracketToken:
+ case StringToken:
+ case BadStringToken:
+ return false;
+ case CommentToken:
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+ }
+
+ // When there are no more tokens to read:
+ // While there are still operator tokens in the stack:
+ while (!stack.isEmpty()) {
+ // If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
+ CSSParserTokenType type = stack.last().type();
+ if (type == LeftParenthesisToken || type == FunctionToken)
+ return false;
+ // Pop the operator onto the output queue.
+ appendOperator(stack.last());
+ stack.removeLast();
+ }
+ return true;
+}
+
+static bool operateOnStack(Vector<SizesCalcValue>& stack, UChar operation)
+{
+ if (stack.size() < 2)
+ return false;
+ SizesCalcValue rightOperand = stack.last();
+ stack.removeLast();
+ SizesCalcValue leftOperand = stack.last();
+ stack.removeLast();
+ bool isLength;
+ switch (operation) {
+ case '+':
+ if (rightOperand.isLength != leftOperand.isLength)
+ return false;
+ isLength = (rightOperand.isLength && leftOperand.isLength);
+ stack.append(SizesCalcValue(leftOperand.value + rightOperand.value, isLength));
+ break;
+ case '-':
+ if (rightOperand.isLength != leftOperand.isLength)
+ return false;
+ isLength = (rightOperand.isLength && leftOperand.isLength);
+ stack.append(SizesCalcValue(leftOperand.value - rightOperand.value, isLength));
+ break;
+ case '*':
+ if (rightOperand.isLength && leftOperand.isLength)
+ return false;
+ isLength = (rightOperand.isLength || leftOperand.isLength);
+ stack.append(SizesCalcValue(leftOperand.value * rightOperand.value, isLength));
+ break;
+ case '/':
+ if (rightOperand.isLength || !rightOperand.value)
+ return false;
+ stack.append(SizesCalcValue(leftOperand.value / rightOperand.value, leftOperand.isLength));
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool SizesCalcParser::calculate()
+{
+ Vector<SizesCalcValue> stack;
+ for (const auto& value : m_valueList) {
+ if (!value.operation)
+ stack.append(value);
+ else {
+ if (!operateOnStack(stack, value.operation))
+ return false;
+ }
+ }
+ if (stack.size() == 1 && stack.last().isLength) {
+ m_result = std::max(clampTo<float>(stack.last().value), (float)0.0);
+ return true;
+ }
+ return false;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/css/parser/SizesCalcParser.h b/Source/WebCore/css/parser/SizesCalcParser.h
new file mode 100644
index 000000000..4ea7c797a
--- /dev/null
+++ b/Source/WebCore/css/parser/SizesCalcParser.h
@@ -0,0 +1,81 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "CSSParserToken.h"
+#include "CSSParserTokenRange.h"
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class Document;
+
+struct SizesCalcValue {
+ double value;
+ bool isLength;
+ UChar operation;
+
+ SizesCalcValue()
+ : value(0)
+ , isLength(false)
+ , operation(0)
+ {
+ }
+
+ SizesCalcValue(double numericValue, bool length)
+ : value(numericValue)
+ , isLength(length)
+ , operation(0)
+ {
+ }
+};
+
+class SizesCalcParser {
+public:
+ SizesCalcParser(CSSParserTokenRange, const Document&);
+
+ float result() const;
+ bool isValid() const { return m_isValid; }
+
+private:
+ bool calcToReversePolishNotation(CSSParserTokenRange);
+ bool calculate();
+ void appendNumber(const CSSParserToken&);
+ bool appendLength(const CSSParserToken&);
+ bool handleOperator(Vector<CSSParserToken>& stack, const CSSParserToken&);
+ void appendOperator(const CSSParserToken&);
+
+ Vector<SizesCalcValue> m_valueList;
+ bool m_isValid;
+ float m_result;
+ const Document& m_document;
+};
+
+} // namespace WebCore