diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/css/parser | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/css/parser')
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 |