blob: 167f96e88df375f57149f2cfcc65a71247133e18 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/css/parser/css_parser_impl.h"
#include <bitset>
#include <memory>
#include <utility>
#include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
#include "third_party/blink/renderer/core/css/css_custom_property_declaration.h"
#include "third_party/blink/renderer/core/css/css_keyframes_rule.h"
#include "third_party/blink/renderer/core/css/css_style_sheet.h"
#include "third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_at_rule_id.h"
#include "third_party/blink/renderer/core/css/parser/css_lazy_parsing_state.h"
#include "third_party/blink/renderer/core/css/parser/css_lazy_property_parser_impl.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_observer.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_selector.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
#include "third_party/blink/renderer/core/css/parser/css_property_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_selector_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_supports_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/css/parser/css_variable_parser.h"
#include "third_party/blink/renderer/core/css/parser/media_query_parser.h"
#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
#include "third_party/blink/renderer/core/css/property_registry.h"
#include "third_party/blink/renderer/core/css/style_rule_counter_style.h"
#include "third_party/blink/renderer/core/css/style_rule_import.h"
#include "third_party/blink/renderer/core/css/style_rule_keyframe.h"
#include "third_party/blink/renderer/core/css/style_rule_namespace.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/frame/deprecation.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
namespace blink {
namespace {
// This may still consume tokens if it fails
AtomicString ConsumeStringOrURI(CSSParserTokenStream& stream) {
const CSSParserToken& token = stream.Peek();
if (token.GetType() == kStringToken || token.GetType() == kUrlToken)
return stream.ConsumeIncludingWhitespace().Value().ToAtomicString();
if (token.GetType() != kFunctionToken ||
!EqualIgnoringASCIICase(token.Value(), "url"))
return AtomicString();
CSSParserTokenStream::BlockGuard guard(stream);
const CSSParserToken& uri = stream.ConsumeIncludingWhitespace();
if (uri.GetType() == kBadStringToken || !stream.UncheckedAtEnd())
return AtomicString();
DCHECK_EQ(uri.GetType(), kStringToken);
return uri.Value().ToAtomicString();
}
} // namespace
CSSParserImpl::CSSParserImpl(const CSSParserContext* context,
StyleSheetContents* style_sheet)
: context_(context),
style_sheet_(style_sheet),
observer_(nullptr),
lazy_state_(nullptr) {}
MutableCSSPropertyValueSet::SetResult CSSParserImpl::ParseValue(
MutableCSSPropertyValueSet* declaration,
CSSPropertyID unresolved_property,
const String& string,
bool important,
const CSSParserContext* context) {
STACK_UNINITIALIZED CSSParserImpl parser(context);
StyleRule::RuleType rule_type = StyleRule::kStyle;
if (declaration->CssParserMode() == kCSSViewportRuleMode)
rule_type = StyleRule::kViewport;
else if (declaration->CssParserMode() == kCSSFontFaceRuleMode)
rule_type = StyleRule::kFontFace;
CSSTokenizer tokenizer(string);
CSSParserTokenStream stream(tokenizer);
CSSTokenizedValue tokenized_value = ConsumeValue(stream);
parser.ConsumeDeclarationValue(tokenized_value, unresolved_property,
important, rule_type);
bool did_parse = false;
bool did_change = false;
if (!parser.parsed_properties_.IsEmpty()) {
did_parse = true;
did_change = declaration->AddParsedProperties(parser.parsed_properties_);
}
return MutableCSSPropertyValueSet::SetResult{did_parse, did_change};
}
MutableCSSPropertyValueSet::SetResult CSSParserImpl::ParseVariableValue(
MutableCSSPropertyValueSet* declaration,
const AtomicString& property_name,
const String& value,
bool important,
const CSSParserContext* context,
bool is_animation_tainted) {
STACK_UNINITIALIZED CSSParserImpl parser(context);
CSSTokenizer tokenizer(value);
CSSParserTokenStream stream(tokenizer);
CSSTokenizedValue tokenized_value = ConsumeValue(stream);
parser.ConsumeVariableValue(tokenized_value, property_name, important,
is_animation_tainted);
bool did_parse = false;
bool did_change = false;
if (!parser.parsed_properties_.IsEmpty()) {
did_parse = true;
did_change = declaration->AddParsedProperties(parser.parsed_properties_);
}
return MutableCSSPropertyValueSet::SetResult{did_parse, did_change};
}
static inline void FilterProperties(
bool important,
const HeapVector<CSSPropertyValue, 256>& input,
HeapVector<CSSPropertyValue, 256>& output,
wtf_size_t& unused_entries,
std::bitset<kNumCSSProperties>& seen_properties,
HashSet<AtomicString>& seen_custom_properties) {
// Add properties in reverse order so that highest priority definitions are
// reached first. Duplicate definitions can then be ignored when found.
for (wtf_size_t i = input.size(); i--;) {
const CSSPropertyValue& property = input[i];
if (property.IsImportant() != important)
continue;
if (property.Id() == CSSPropertyID::kVariable) {
const AtomicString& name =
To<CSSCustomPropertyDeclaration>(property.Value())->GetName();
if (seen_custom_properties.Contains(name))
continue;
seen_custom_properties.insert(name);
} else {
const unsigned property_id_index = GetCSSPropertyIDIndex(property.Id());
if (seen_properties.test(property_id_index))
continue;
seen_properties.set(property_id_index);
}
output[--unused_entries] = property;
}
}
static ImmutableCSSPropertyValueSet* CreateCSSPropertyValueSet(
HeapVector<CSSPropertyValue, 256>& parsed_properties,
CSSParserMode mode) {
std::bitset<kNumCSSProperties> seen_properties;
wtf_size_t unused_entries = parsed_properties.size();
HeapVector<CSSPropertyValue, 256> results(unused_entries);
HashSet<AtomicString> seen_custom_properties;
FilterProperties(true, parsed_properties, results, unused_entries,
seen_properties, seen_custom_properties);
FilterProperties(false, parsed_properties, results, unused_entries,
seen_properties, seen_custom_properties);
ImmutableCSSPropertyValueSet* result = ImmutableCSSPropertyValueSet::Create(
results.data() + unused_entries, results.size() - unused_entries, mode);
parsed_properties.clear();
return result;
}
ImmutableCSSPropertyValueSet* CSSParserImpl::ParseInlineStyleDeclaration(
const String& string,
Element* element) {
Document& document = element->GetDocument();
auto* context = MakeGarbageCollected<CSSParserContext>(
document.ElementSheet().Contents()->ParserContext(), &document);
CSSParserMode mode = element->IsHTMLElement() && !document.InQuirksMode()
? kHTMLStandardMode
: kHTMLQuirksMode;
context->SetMode(mode);
CSSParserImpl parser(context, document.ElementSheet().Contents());
CSSTokenizer tokenizer(string);
CSSParserTokenStream stream(tokenizer);
parser.ConsumeDeclarationList(stream, StyleRule::kStyle);
return CreateCSSPropertyValueSet(parser.parsed_properties_, mode);
}
ImmutableCSSPropertyValueSet* CSSParserImpl::ParseInlineStyleDeclaration(
const String& string,
CSSParserMode parser_mode,
SecureContextMode secure_context_mode) {
auto* context =
MakeGarbageCollected<CSSParserContext>(parser_mode, secure_context_mode);
CSSParserImpl parser(context);
CSSTokenizer tokenizer(string);
CSSParserTokenStream stream(tokenizer);
parser.ConsumeDeclarationList(stream, StyleRule::kStyle);
return CreateCSSPropertyValueSet(parser.parsed_properties_, parser_mode);
}
bool CSSParserImpl::ParseDeclarationList(
MutableCSSPropertyValueSet* declaration,
const String& string,
const CSSParserContext* context) {
CSSParserImpl parser(context);
StyleRule::RuleType rule_type = StyleRule::kStyle;
if (declaration->CssParserMode() == kCSSViewportRuleMode)
rule_type = StyleRule::kViewport;
CSSTokenizer tokenizer(string);
CSSParserTokenStream stream(tokenizer);
parser.ConsumeDeclarationList(stream, rule_type);
if (parser.parsed_properties_.IsEmpty())
return false;
std::bitset<kNumCSSProperties> seen_properties;
wtf_size_t unused_entries = parser.parsed_properties_.size();
HeapVector<CSSPropertyValue, 256> results(unused_entries);
HashSet<AtomicString> seen_custom_properties;
FilterProperties(true, parser.parsed_properties_, results, unused_entries,
seen_properties, seen_custom_properties);
FilterProperties(false, parser.parsed_properties_, results, unused_entries,
seen_properties, seen_custom_properties);
if (unused_entries)
results.EraseAt(0, unused_entries);
return declaration->AddParsedProperties(results);
}
StyleRuleBase* CSSParserImpl::ParseRule(const String& string,
const CSSParserContext* context,
StyleSheetContents* style_sheet,
AllowedRulesType allowed_rules) {
CSSParserImpl parser(context, style_sheet);
CSSTokenizer tokenizer(string);
CSSParserTokenStream stream(tokenizer);
stream.ConsumeWhitespace();
if (stream.UncheckedAtEnd())
return nullptr; // Parse error, empty rule
StyleRuleBase* rule;
if (stream.UncheckedPeek().GetType() == kAtKeywordToken)
rule = parser.ConsumeAtRule(stream, allowed_rules);
else
rule = parser.ConsumeQualifiedRule(stream, allowed_rules);
if (!rule)
return nullptr; // Parse error, failed to consume rule
stream.ConsumeWhitespace();
if (!rule || !stream.UncheckedAtEnd())
return nullptr; // Parse error, trailing garbage
return rule;
}
ParseSheetResult CSSParserImpl::ParseStyleSheet(
const String& string,
const CSSParserContext* context,
StyleSheetContents* style_sheet,
CSSDeferPropertyParsing defer_property_parsing,
bool allow_import_rules) {
TRACE_EVENT_BEGIN2("blink,blink_style", "CSSParserImpl::parseStyleSheet",
"baseUrl", context->BaseURL().GetString().Utf8(), "mode",
context->Mode());
TRACE_EVENT_BEGIN0("blink,blink_style",
"CSSParserImpl::parseStyleSheet.parse");
CSSTokenizer tokenizer(string);
CSSParserTokenStream stream(tokenizer);
CSSParserImpl parser(context, style_sheet);
if (defer_property_parsing == CSSDeferPropertyParsing::kYes) {
parser.lazy_state_ = MakeGarbageCollected<CSSLazyParsingState>(
context, string, parser.style_sheet_);
}
ParseSheetResult result = ParseSheetResult::kSucceeded;
bool first_rule_valid = parser.ConsumeRuleList(
stream, kTopLevelRuleList,
[&style_sheet, &result, allow_import_rules,
context](StyleRuleBase* rule) {
if (rule->IsCharsetRule())
return;
if (rule->IsImportRule()) {
if (!allow_import_rules || context->IsForMarkupSanitization()) {
result = ParseSheetResult::kHasUnallowedImportRule;
return;
}
}
style_sheet->ParserAppendRule(rule);
});
style_sheet->SetHasSyntacticallyValidCSSHeader(first_rule_valid);
TRACE_EVENT_END0("blink,blink_style", "CSSParserImpl::parseStyleSheet.parse");
TRACE_EVENT_END2("blink,blink_style", "CSSParserImpl::parseStyleSheet",
"tokenCount", tokenizer.TokenCount(), "length",
string.length());
return result;
}
CSSSelectorList CSSParserImpl::ParsePageSelector(
CSSParserTokenRange range,
StyleSheetContents* style_sheet) {
// We only support a small subset of the css-page spec.
range.ConsumeWhitespace();
AtomicString type_selector;
if (range.Peek().GetType() == kIdentToken)
type_selector = range.Consume().Value().ToAtomicString();
AtomicString pseudo;
if (range.Peek().GetType() == kColonToken) {
range.Consume();
if (range.Peek().GetType() != kIdentToken)
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 (!type_selector.IsNull() && pseudo.IsNull()) {
selector = std::make_unique<CSSParserSelector>(
QualifiedName(g_null_atom, type_selector, g_star_atom));
} else {
selector = std::make_unique<CSSParserSelector>();
if (!pseudo.IsNull()) {
selector->SetMatch(CSSSelector::kPagePseudoClass);
selector->UpdatePseudoPage(pseudo.LowerASCII());
if (selector->GetPseudoType() == CSSSelector::kPseudoUnknown)
return CSSSelectorList();
}
if (!type_selector.IsNull()) {
selector->PrependTagSelector(
QualifiedName(g_null_atom, type_selector, g_star_atom));
}
}
selector->SetForPage();
Vector<std::unique_ptr<CSSParserSelector>> selector_vector;
selector_vector.push_back(std::move(selector));
CSSSelectorList selector_list =
CSSSelectorList::AdoptSelectorVector(selector_vector);
return selector_list;
}
std::unique_ptr<Vector<double>> CSSParserImpl::ParseKeyframeKeyList(
const String& key_list) {
CSSTokenizer tokenizer(key_list);
// TODO(crbug.com/661854): Use streams instead of ranges
return ConsumeKeyframeKeyList(CSSParserTokenRange(tokenizer.TokenizeToEOF()));
}
bool CSSParserImpl::ConsumeSupportsDeclaration(CSSParserTokenStream& stream) {
DCHECK(parsed_properties_.IsEmpty());
// Even though we might use an observer here, this is just to test if we
// successfully parse the range, so we can temporarily remove the observer.
CSSParserObserver* observer_copy = observer_;
observer_ = nullptr;
CSSParserTokenStream::RangeBoundary range_boundary(
stream, CSSParserTokenType::kRightParenthesisToken);
ConsumeDeclaration(stream, StyleRule::kStyle);
observer_ = observer_copy;
bool result = !parsed_properties_.IsEmpty();
parsed_properties_.clear();
return result;
}
void CSSParserImpl::ParseDeclarationListForInspector(
const String& declaration,
const CSSParserContext* context,
CSSParserObserver& observer) {
CSSParserImpl parser(context);
parser.observer_ = &observer;
CSSTokenizer tokenizer(declaration);
observer.StartRuleHeader(StyleRule::kStyle, 0);
observer.EndRuleHeader(1);
CSSParserTokenStream stream(tokenizer);
parser.ConsumeDeclarationList(stream, StyleRule::kStyle);
}
void CSSParserImpl::ParseStyleSheetForInspector(const String& string,
const CSSParserContext* context,
StyleSheetContents* style_sheet,
CSSParserObserver& observer) {
CSSParserImpl parser(context, style_sheet);
parser.observer_ = &observer;
CSSTokenizer tokenizer(string);
CSSParserTokenStream stream(tokenizer);
bool first_rule_valid = parser.ConsumeRuleList(
stream, kTopLevelRuleList, [&style_sheet](StyleRuleBase* rule) {
if (rule->IsCharsetRule())
return;
style_sheet->ParserAppendRule(rule);
});
style_sheet->SetHasSyntacticallyValidCSSHeader(first_rule_valid);
}
CSSPropertyValueSet* CSSParserImpl::ParseDeclarationListForLazyStyle(
const String& string,
wtf_size_t offset,
const CSSParserContext* context) {
CSSTokenizer tokenizer(string, offset);
CSSParserTokenStream stream(tokenizer);
CSSParserTokenStream::BlockGuard guard(stream);
CSSParserImpl parser(context);
parser.ConsumeDeclarationList(stream, StyleRule::kStyle);
return CreateCSSPropertyValueSet(parser.parsed_properties_, context->Mode());
}
static CSSParserImpl::AllowedRulesType ComputeNewAllowedRules(
CSSParserImpl::AllowedRulesType allowed_rules,
StyleRuleBase* rule) {
if (!rule || allowed_rules == CSSParserImpl::kKeyframeRules ||
allowed_rules == CSSParserImpl::kFontFeatureRules ||
allowed_rules == CSSParserImpl::kNoRules)
return allowed_rules;
DCHECK_LE(allowed_rules, CSSParserImpl::kRegularRules);
if (rule->IsCharsetRule() || rule->IsImportRule())
return CSSParserImpl::kAllowImportRules;
if (rule->IsNamespaceRule())
return CSSParserImpl::kAllowNamespaceRules;
return CSSParserImpl::kRegularRules;
}
template <typename T>
bool CSSParserImpl::ConsumeRuleList(CSSParserTokenStream& stream,
RuleListType rule_list_type,
const T callback) {
AllowedRulesType allowed_rules = kRegularRules;
switch (rule_list_type) {
case kTopLevelRuleList:
allowed_rules = kAllowCharsetRules;
break;
case kRegularRuleList:
allowed_rules = kRegularRules;
break;
case kKeyframesRuleList:
allowed_rules = kKeyframeRules;
break;
case kFontFeatureRuleList:
allowed_rules = kFontFeatureRules;
break;
default:
NOTREACHED();
}
bool seen_rule = false;
bool first_rule_valid = false;
while (!stream.AtEnd()) {
StyleRuleBase* rule;
switch (stream.UncheckedPeek().GetType()) {
case kWhitespaceToken:
stream.UncheckedConsume();
continue;
case kAtKeywordToken:
rule = ConsumeAtRule(stream, allowed_rules);
break;
case kCDOToken:
case kCDCToken:
if (rule_list_type == kTopLevelRuleList) {
stream.UncheckedConsume();
continue;
}
FALLTHROUGH;
default:
rule = ConsumeQualifiedRule(stream, allowed_rules);
break;
}
if (!seen_rule) {
seen_rule = true;
first_rule_valid = rule;
}
if (rule) {
allowed_rules = ComputeNewAllowedRules(allowed_rules, rule);
callback(rule);
}
}
return first_rule_valid;
}
CSSParserTokenRange ConsumeAtRulePrelude(CSSParserTokenStream& stream) {
return stream.ConsumeUntilPeekedTypeIs<kLeftBraceToken, kSemicolonToken>();
}
bool ConsumeEndOfPreludeForAtRuleWithoutBlock(CSSParserTokenStream& stream) {
if (stream.AtEnd() || stream.UncheckedPeek().GetType() == kSemicolonToken) {
if (!stream.UncheckedAtEnd())
stream.UncheckedConsume(); // kSemicolonToken
return true;
}
// Consume the erroneous block.
CSSParserTokenStream::BlockGuard guard(stream);
return false; // Parse error, we expected no block.
}
bool ConsumeEndOfPreludeForAtRuleWithBlock(CSSParserTokenStream& stream) {
if (stream.AtEnd() || stream.UncheckedPeek().GetType() == kSemicolonToken) {
if (!stream.UncheckedAtEnd())
stream.UncheckedConsume(); // kSemicolonToken
return false; // Parse error, we expected a block.
}
return true;
}
void ConsumeErroneousAtRule(CSSParserTokenStream& stream) {
// Consume the prelude and block if present.
ConsumeAtRulePrelude(stream);
if (!stream.AtEnd()) {
if (stream.UncheckedPeek().GetType() == kLeftBraceToken)
CSSParserTokenStream::BlockGuard guard(stream);
else
stream.UncheckedConsume(); // kSemicolonToken
}
}
StyleRuleBase* CSSParserImpl::ConsumeAtRule(CSSParserTokenStream& stream,
AllowedRulesType allowed_rules) {
DCHECK_EQ(stream.Peek().GetType(), kAtKeywordToken);
const StringView name = stream.ConsumeIncludingWhitespace().Value();
const CSSAtRuleID id = CssAtRuleID(name);
// @import rules have a URI component that is not technically part of the
// prelude.
AtomicString import_prelude_uri;
if (allowed_rules <= kAllowImportRules && id == kCSSAtRuleImport)
import_prelude_uri = ConsumeStringOrURI(stream);
if (id != kCSSAtRuleInvalid && context_->IsUseCounterRecordingEnabled())
CountAtRule(context_, id);
if (allowed_rules == kKeyframeRules || allowed_rules == kFontFeatureRules ||
allowed_rules == kNoRules) {
// Parse error, no at-rules supported inside @keyframes,
// @font-feature-values, or blocks supported inside declaration lists.
ConsumeErroneousAtRule(stream);
return nullptr;
}
stream.EnsureLookAhead();
if (allowed_rules == kAllowCharsetRules && id == kCSSAtRuleCharset) {
return ConsumeCharsetRule(stream);
} else if (allowed_rules <= kAllowImportRules && id == kCSSAtRuleImport) {
return ConsumeImportRule(std::move(import_prelude_uri), stream);
} else if (allowed_rules <= kAllowNamespaceRules &&
id == kCSSAtRuleNamespace) {
return ConsumeNamespaceRule(stream);
} else {
DCHECK_LE(allowed_rules, kRegularRules);
switch (id) {
case kCSSAtRuleContainer:
return ConsumeContainerRule(stream);
case kCSSAtRuleMedia:
return ConsumeMediaRule(stream);
case kCSSAtRuleSupports:
return ConsumeSupportsRule(stream);
case kCSSAtRuleViewport:
return ConsumeViewportRule(stream);
case kCSSAtRuleFontFace:
return ConsumeFontFaceRule(stream);
case kCSSAtRuleWebkitKeyframes:
return ConsumeKeyframesRule(true, stream);
case kCSSAtRuleKeyframes:
return ConsumeKeyframesRule(false, stream);
case kCSSAtRulePage:
return ConsumePageRule(stream);
case kCSSAtRuleProperty:
return ConsumePropertyRule(stream);
case kCSSAtRuleScrollTimeline:
return ConsumeScrollTimelineRule(stream);
case kCSSAtRuleCounterStyle:
return ConsumeCounterStyleRule(stream);
default:
ConsumeErroneousAtRule(stream);
return nullptr; // Parse error, unrecognised or not-allowed at-rule
}
}
}
StyleRuleBase* CSSParserImpl::ConsumeQualifiedRule(
CSSParserTokenStream& stream,
AllowedRulesType allowed_rules) {
if (allowed_rules <= kRegularRules) {
return ConsumeStyleRule(stream);
}
if (allowed_rules == kKeyframeRules) {
stream.EnsureLookAhead();
const wtf_size_t prelude_offset_start = stream.LookAheadOffset();
const CSSParserTokenRange prelude =
stream.ConsumeUntilPeekedTypeIs<kLeftBraceToken>();
const RangeOffset prelude_offset(prelude_offset_start,
stream.LookAheadOffset());
if (stream.AtEnd())
return nullptr; // Parse error, EOF instead of qualified rule block
CSSParserTokenStream::BlockGuard guard(stream);
StyleRuleKeyframe* keyframe_style_rule =
ConsumeKeyframeStyleRule(prelude, prelude_offset, stream);
if (keyframe_style_rule)
context_->ReportLayoutAnimationsViolationIfNeeded(*keyframe_style_rule);
return keyframe_style_rule;
}
if (allowed_rules == kFontFeatureRules) {
stream.ConsumeWhitespace();
if (stream.AtEnd())
return nullptr; // Parse error, EOF instead of qualified rule block
bool prelude_invalid = false;
stream.EnsureLookAhead();
if (stream.UncheckedPeek().GetType() != kLeftBraceToken) {
prelude_invalid = true;
while (!stream.AtEnd() &&
stream.UncheckedPeek().GetType() != kLeftBraceToken)
stream.UncheckedConsumeComponentValue();
if (stream.AtEnd())
return nullptr;
}
CSSParserTokenStream::BlockGuard guard(stream);
if (prelude_invalid)
return nullptr;
ConsumeDeclarationList(stream, StyleRule::kFontFace);
return MakeGarbageCollected<StyleRuleFontFace>(
CreateCSSPropertyValueSet(parsed_properties_, kCSSFontFaceRuleMode));
}
NOTREACHED();
return nullptr;
}
// This may still consume tokens if it fails
static AtomicString ConsumeStringOrURI(CSSParserTokenRange& range) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kStringToken || token.GetType() == kUrlToken)
return range.ConsumeIncludingWhitespace().Value().ToAtomicString();
if (token.GetType() != kFunctionToken ||
!EqualIgnoringASCIICase(token.Value(), "url"))
return AtomicString();
CSSParserTokenRange contents = range.ConsumeBlock();
const CSSParserToken& uri = contents.ConsumeIncludingWhitespace();
if (uri.GetType() == kBadStringToken || !contents.AtEnd())
return AtomicString();
DCHECK_EQ(uri.GetType(), kStringToken);
return uri.Value().ToAtomicString();
}
StyleRuleCharset* CSSParserImpl::ConsumeCharsetRule(
CSSParserTokenStream& stream) {
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
if (!ConsumeEndOfPreludeForAtRuleWithoutBlock(stream))
return nullptr;
const CSSParserToken& string = prelude.ConsumeIncludingWhitespace();
if (string.GetType() != kStringToken || !prelude.AtEnd())
return nullptr; // Parse error, expected a single string
return MakeGarbageCollected<StyleRuleCharset>();
}
StyleRuleImport* CSSParserImpl::ConsumeImportRule(
AtomicString uri,
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithoutBlock(stream))
return nullptr;
if (uri.IsNull())
return nullptr; // Parse error, expected string or URI
if (observer_) {
observer_->StartRuleHeader(StyleRule::kImport, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
observer_->StartRuleBody(prelude_offset_end);
observer_->EndRuleBody(prelude_offset_end);
}
return MakeGarbageCollected<StyleRuleImport>(
uri,
MediaQueryParser::ParseMediaQuerySet(prelude,
context_->GetExecutionContext()),
context_->IsOriginClean() ? OriginClean::kTrue : OriginClean::kFalse);
}
StyleRuleNamespace* CSSParserImpl::ConsumeNamespaceRule(
CSSParserTokenStream& stream) {
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
if (!ConsumeEndOfPreludeForAtRuleWithoutBlock(stream))
return nullptr;
AtomicString namespace_prefix;
if (prelude.Peek().GetType() == kIdentToken)
namespace_prefix =
prelude.ConsumeIncludingWhitespace().Value().ToAtomicString();
AtomicString uri(ConsumeStringOrURI(prelude));
if (uri.IsNull() || !prelude.AtEnd())
return nullptr; // Parse error, expected string or URI
return MakeGarbageCollected<StyleRuleNamespace>(namespace_prefix, uri);
}
StyleRuleMedia* CSSParserImpl::ConsumeMediaRule(CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
HeapVector<Member<StyleRuleBase>> rules;
if (observer_) {
observer_->StartRuleHeader(StyleRule::kMedia, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
observer_->StartRuleBody(stream.Offset());
}
if (style_sheet_)
style_sheet_->SetHasMediaQueries();
const auto media = MediaQueryParser::ParseMediaQuerySet(
prelude, context_->GetExecutionContext());
ConsumeRuleList(stream, kRegularRuleList,
[&rules](StyleRuleBase* rule) { rules.push_back(rule); });
if (observer_)
observer_->EndRuleBody(stream.Offset());
return MakeGarbageCollected<StyleRuleMedia>(media, rules);
}
StyleRuleSupports* CSSParserImpl::ConsumeSupportsRule(
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSSupportsParser::Result supported =
CSSSupportsParser::ConsumeSupportsCondition(stream, *this);
// Check whether the entire prelude was consumed. If it wasn't, ensure we
// consume any leftovers plus the block before returning a parse error.
stream.ConsumeWhitespace();
CSSParserTokenRange prelude_remainder = ConsumeAtRulePrelude(stream);
if (!prelude_remainder.AtEnd())
supported = CSSSupportsParser::Result::kParseFailure;
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
if (supported == CSSSupportsParser::Result::kParseFailure)
return nullptr; // Parse error, invalid @supports condition
if (observer_) {
observer_->StartRuleHeader(StyleRule::kSupports, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
observer_->StartRuleBody(stream.Offset());
}
const auto prelude_serialized =
stream
.StringRangeAt(prelude_offset_start,
prelude_offset_end - prelude_offset_start)
.ToString()
.SimplifyWhiteSpace();
HeapVector<Member<StyleRuleBase>> rules;
ConsumeRuleList(stream, kRegularRuleList,
[&rules](StyleRuleBase* rule) { rules.push_back(rule); });
if (observer_)
observer_->EndRuleBody(stream.Offset());
return MakeGarbageCollected<StyleRuleSupports>(
prelude_serialized, supported == CSSSupportsParser::Result::kSupported,
rules);
}
StyleRuleViewport* CSSParserImpl::ConsumeViewportRule(
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
const CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
// Allow @viewport rules from UA stylesheets only.
if (!IsUASheetBehavior(context_->Mode()))
return nullptr;
if (!prelude.AtEnd())
return nullptr; // Parser error; @viewport prelude should be empty
if (observer_) {
observer_->StartRuleHeader(StyleRule::kViewport, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
observer_->StartRuleBody(prelude_offset_end);
observer_->EndRuleBody(prelude_offset_end);
}
if (style_sheet_)
style_sheet_->SetHasViewportRule();
ConsumeDeclarationList(stream, StyleRule::kViewport);
return MakeGarbageCollected<StyleRuleViewport>(
CreateCSSPropertyValueSet(parsed_properties_, kCSSViewportRuleMode));
}
StyleRuleFontFace* CSSParserImpl::ConsumeFontFaceRule(
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
if (!prelude.AtEnd())
return nullptr; // Parse error; @font-face prelude should be empty
if (observer_) {
observer_->StartRuleHeader(StyleRule::kFontFace, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
observer_->StartRuleBody(prelude_offset_end);
observer_->EndRuleBody(prelude_offset_end);
}
if (style_sheet_)
style_sheet_->SetHasFontFaceRule();
ConsumeDeclarationList(stream, StyleRule::kFontFace);
return MakeGarbageCollected<StyleRuleFontFace>(
CreateCSSPropertyValueSet(parsed_properties_, kCSSFontFaceRuleMode));
}
StyleRuleKeyframes* CSSParserImpl::ConsumeKeyframesRule(
bool webkit_prefixed,
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
const CSSParserToken& name_token = prelude.ConsumeIncludingWhitespace();
if (!prelude.AtEnd())
return nullptr; // Parse error; expected single non-whitespace token in
// @keyframes header
String name;
if (name_token.GetType() == kIdentToken) {
name = name_token.Value().ToString();
} else if (name_token.GetType() == kStringToken && webkit_prefixed) {
context_->Count(WebFeature::kQuotedKeyframesRule);
name = name_token.Value().ToString();
} else {
return nullptr; // Parse error; expected ident token in @keyframes header
}
if (observer_) {
observer_->StartRuleHeader(StyleRule::kKeyframes, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
observer_->StartRuleBody(stream.Offset());
}
auto* keyframe_rule = MakeGarbageCollected<StyleRuleKeyframes>();
ConsumeRuleList(
stream, kKeyframesRuleList, [keyframe_rule](StyleRuleBase* keyframe) {
keyframe_rule->ParserAppendKeyframe(To<StyleRuleKeyframe>(keyframe));
});
keyframe_rule->SetName(name);
keyframe_rule->SetVendorPrefixed(webkit_prefixed);
if (observer_)
observer_->EndRuleBody(stream.Offset());
return keyframe_rule;
}
StyleRulePage* CSSParserImpl::ConsumePageRule(CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
CSSSelectorList selector_list = ParsePageSelector(prelude, style_sheet_);
if (!selector_list.IsValid())
return nullptr; // Parse error, invalid @page selector
if (observer_) {
observer_->StartRuleHeader(StyleRule::kPage, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
}
ConsumeDeclarationList(stream, StyleRule::kStyle);
return MakeGarbageCollected<StyleRulePage>(
std::move(selector_list),
CreateCSSPropertyValueSet(parsed_properties_, context_->Mode()));
}
StyleRuleProperty* CSSParserImpl::ConsumePropertyRule(
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
if (!RuntimeEnabledFeatures::CSSVariables2AtPropertyEnabled())
return nullptr;
const CSSParserToken& name_token = prelude.ConsumeIncludingWhitespace();
if (!prelude.AtEnd())
return nullptr;
if (!CSSVariableParser::IsValidVariableName(name_token))
return nullptr;
String name = name_token.Value().ToString();
if (observer_) {
observer_->StartRuleHeader(StyleRule::kProperty, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
}
ConsumeDeclarationList(stream, StyleRule::kProperty);
return MakeGarbageCollected<StyleRuleProperty>(
name, CreateCSSPropertyValueSet(parsed_properties_, context_->Mode()));
}
StyleRuleCounterStyle* CSSParserImpl::ConsumeCounterStyleRule(
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
AtomicString name = css_parsing_utils::ConsumeCounterStyleNameInPrelude(
prelude, *GetContext());
if (!name)
return nullptr;
if (observer_) {
observer_->StartRuleHeader(StyleRule::kCounterStyle, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
}
ConsumeDeclarationList(stream, StyleRule::kCounterStyle);
return MakeGarbageCollected<StyleRuleCounterStyle>(
name, CreateCSSPropertyValueSet(parsed_properties_, context_->Mode()));
}
StyleRuleScrollTimeline* CSSParserImpl::ConsumeScrollTimelineRule(
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
if (!RuntimeEnabledFeatures::CSSScrollTimelineEnabled())
return nullptr;
const CSSParserToken& name_token = prelude.ConsumeIncludingWhitespace();
if (!prelude.AtEnd())
return nullptr;
if (!css_parsing_utils::IsTimelineName(name_token))
return nullptr;
String name = name_token.Value().ToString();
if (observer_) {
observer_->StartRuleHeader(StyleRule::kScrollTimeline,
prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
}
ConsumeDeclarationList(stream, StyleRule::kScrollTimeline);
return MakeGarbageCollected<StyleRuleScrollTimeline>(
name, CreateCSSPropertyValueSet(parsed_properties_, context_->Mode()));
}
StyleRuleContainer* CSSParserImpl::ConsumeContainerRule(
CSSParserTokenStream& stream) {
wtf_size_t prelude_offset_start = stream.LookAheadOffset();
CSSParserTokenRange prelude = ConsumeAtRulePrelude(stream);
wtf_size_t prelude_offset_end = stream.LookAheadOffset();
if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream))
return nullptr;
CSSParserTokenStream::BlockGuard guard(stream);
if (observer_) {
observer_->StartRuleHeader(StyleRule::kContainer, prelude_offset_start);
observer_->EndRuleHeader(prelude_offset_end);
observer_->StartRuleBody(stream.Offset());
}
// TODO(crbug.com/1145970): Restrict what is allowed by @container.
scoped_refptr<MediaQuerySet> media_queries =
MediaQueryParser::ParseMediaQuerySet(prelude,
context_->GetExecutionContext());
if (!media_queries)
return nullptr;
ContainerQuery* container_query =
MakeGarbageCollected<ContainerQuery>(media_queries);
HeapVector<Member<StyleRuleBase>> rules;
ConsumeRuleList(stream, kRegularRuleList,
[&rules](StyleRuleBase* rule) { rules.push_back(rule); });
if (observer_)
observer_->EndRuleBody(stream.Offset());
return MakeGarbageCollected<StyleRuleContainer>(*container_query, rules);
}
StyleRuleKeyframe* CSSParserImpl::ConsumeKeyframeStyleRule(
const CSSParserTokenRange prelude,
const RangeOffset& prelude_offset,
CSSParserTokenStream& block) {
std::unique_ptr<Vector<double>> key_list = ConsumeKeyframeKeyList(prelude);
if (!key_list)
return nullptr;
if (observer_) {
observer_->StartRuleHeader(StyleRule::kKeyframe, prelude_offset.start);
observer_->EndRuleHeader(prelude_offset.end);
}
ConsumeDeclarationList(block, StyleRule::kKeyframe);
return MakeGarbageCollected<StyleRuleKeyframe>(
std::move(key_list),
CreateCSSPropertyValueSet(parsed_properties_, context_->Mode()));
}
StyleRule* CSSParserImpl::ConsumeStyleRule(CSSParserTokenStream& stream) {
if (observer_)
observer_->StartRuleHeader(StyleRule::kStyle, stream.LookAheadOffset());
// Parse the prelude of the style rule
CSSSelectorList selector_list = CSSSelectorParser::ConsumeSelector(
stream, context_, style_sheet_, observer_);
if (!selector_list.IsValid()) {
// Read the rest of the prelude if there was an error
stream.EnsureLookAhead();
while (!stream.UncheckedAtEnd() &&
stream.UncheckedPeek().GetType() != kLeftBraceToken)
stream.UncheckedConsumeComponentValue();
}
if (observer_)
observer_->EndRuleHeader(stream.LookAheadOffset());
if (stream.AtEnd())
return nullptr; // Parse error, EOF instead of qualified rule block
DCHECK_EQ(stream.Peek().GetType(), kLeftBraceToken);
CSSParserTokenStream::BlockGuard guard(stream);
if (!selector_list.IsValid())
return nullptr; // Parse error, invalid selector list
// TODO(csharrison): How should we lazily parse css that needs the observer?
if (!observer_ && lazy_state_) {
DCHECK(style_sheet_);
return MakeGarbageCollected<StyleRule>(
std::move(selector_list),
MakeGarbageCollected<CSSLazyPropertyParserImpl>(stream.Offset() - 1,
lazy_state_));
}
ConsumeDeclarationList(stream, StyleRule::kStyle);
return MakeGarbageCollected<StyleRule>(
std::move(selector_list),
CreateCSSPropertyValueSet(parsed_properties_, context_->Mode()));
}
void CSSParserImpl::ConsumeDeclarationList(CSSParserTokenStream& stream,
StyleRule::RuleType rule_type) {
DCHECK(parsed_properties_.IsEmpty());
bool use_observer = observer_ && (rule_type == StyleRule::kStyle ||
rule_type == StyleRule::kProperty ||
rule_type == StyleRule::kContainer ||
rule_type == StyleRule::kCounterStyle ||
rule_type == StyleRule::kScrollTimeline ||
rule_type == StyleRule::kKeyframe);
if (use_observer) {
observer_->StartRuleBody(stream.Offset());
}
while (true) {
// Having a lookahead may skip comments, which are used by the observer.
DCHECK(!stream.HasLookAhead() || stream.AtEnd());
if (use_observer && !stream.HasLookAhead()) {
while (true) {
wtf_size_t start_offset = stream.Offset();
if (!stream.ConsumeCommentOrNothing())
break;
observer_->ObserveComment(start_offset, stream.Offset());
}
}
if (stream.AtEnd())
break;
switch (stream.UncheckedPeek().GetType()) {
case kWhitespaceToken:
case kSemicolonToken:
stream.UncheckedConsume();
break;
case kIdentToken: {
CSSParserTokenStream::RangeBoundary range_boundary(stream,
kSemicolonToken);
ConsumeDeclaration(stream, rule_type);
if (!stream.AtEnd())
stream.UncheckedConsume(); // kSemicolonToken
break;
}
default:
while (!stream.UncheckedAtEnd() &&
stream.UncheckedPeek().GetType() != kSemicolonToken) {
stream.UncheckedConsumeComponentValue();
}
if (!stream.UncheckedAtEnd())
stream.UncheckedConsume(); // kSemicolonToken
break;
}
}
if (use_observer)
observer_->EndRuleBody(stream.LookAheadOffset());
}
void CSSParserImpl::ConsumeDeclaration(CSSParserTokenStream& stream,
StyleRule::RuleType rule_type) {
const wtf_size_t decl_offset_start = stream.Offset();
DCHECK_EQ(stream.Peek().GetType(), kIdentToken);
const CSSParserToken& lhs = stream.ConsumeIncludingWhitespace();
if (stream.Peek().GetType() != kColonToken) {
// Parse error.
// Consume the remainder of the declaration for recovery before returning.
stream.ConsumeUntilPeekedBoundary();
return;
}
stream.UncheckedConsume(); // kColonToken
CSSTokenizedValue tokenized_value = ConsumeValue(stream);
bool important = RemoveImportantAnnotationIfPresent(tokenized_value);
size_t properties_count = parsed_properties_.size();
CSSPropertyID unresolved_property = CSSPropertyID::kInvalid;
AtRuleDescriptorID atrule_id = AtRuleDescriptorID::Invalid;
if (rule_type == StyleRule::kFontFace || rule_type == StyleRule::kProperty ||
rule_type == StyleRule::kCounterStyle ||
rule_type == StyleRule::kScrollTimeline) {
if (important) // Invalid
return;
atrule_id = lhs.ParseAsAtRuleDescriptorID();
AtRuleDescriptorParser::ParseAtRule(rule_type, atrule_id, tokenized_value,
*context_, parsed_properties_);
} else {
unresolved_property = lhs.ParseAsUnresolvedCSSPropertyID(
context_->GetExecutionContext(), context_->Mode());
}
// @rules other than FontFace still handled with legacy code.
if (important && rule_type == StyleRule::kKeyframe)
return;
if (unresolved_property == CSSPropertyID::kVariable) {
if (rule_type != StyleRule::kStyle && rule_type != StyleRule::kKeyframe)
return;
AtomicString variable_name = lhs.Value().ToAtomicString();
bool is_animation_tainted = rule_type == StyleRule::kKeyframe;
ConsumeVariableValue(tokenized_value, variable_name, important,
is_animation_tainted);
} else if (unresolved_property != CSSPropertyID::kInvalid) {
if (style_sheet_ && style_sheet_->SingleOwnerDocument())
Deprecation::WarnOnDeprecatedProperties(
style_sheet_->SingleOwnerDocument()->GetFrame(), unresolved_property);
ConsumeDeclarationValue(tokenized_value, unresolved_property, important,
rule_type);
}
if (observer_ &&
(rule_type == StyleRule::kStyle || rule_type == StyleRule::kKeyframe)) {
// The end offset is the offset of the terminating token, which is peeked
// but not yet consumed.
observer_->ObserveProperty(decl_offset_start, stream.LookAheadOffset(),
important,
parsed_properties_.size() != properties_count);
}
}
void CSSParserImpl::ConsumeVariableValue(
const CSSTokenizedValue& tokenized_value,
const AtomicString& variable_name,
bool important,
bool is_animation_tainted) {
if (CSSCustomPropertyDeclaration* value =
CSSVariableParser::ParseDeclarationValue(
variable_name, tokenized_value, is_animation_tainted,
*context_)) {
parsed_properties_.push_back(
CSSPropertyValue(CSSPropertyName(variable_name), *value, important));
context_->Count(context_->Mode(), CSSPropertyID::kVariable);
}
}
void CSSParserImpl::ConsumeDeclarationValue(
const CSSTokenizedValue& tokenized_value,
CSSPropertyID unresolved_property,
bool important,
StyleRule::RuleType rule_type) {
CSSPropertyParser::ParseValue(unresolved_property, important,
tokenized_value.range, context_,
parsed_properties_, rule_type);
}
CSSTokenizedValue CSSParserImpl::ConsumeValue(CSSParserTokenStream& stream) {
stream.EnsureLookAhead();
wtf_size_t value_start_offset = stream.LookAheadOffset();
CSSParserTokenRange range = stream.ConsumeUntilPeekedBoundary();
wtf_size_t value_end_offset = stream.LookAheadOffset();
return {range, stream.StringRangeAt(value_start_offset,
value_end_offset - value_start_offset)};
}
bool CSSParserImpl::RemoveImportantAnnotationIfPresent(
CSSTokenizedValue& tokenized_value) {
const CSSParserToken* first = tokenized_value.range.begin();
const CSSParserToken* last = tokenized_value.range.end() - 1;
while (last >= first && last->GetType() == kWhitespaceToken)
--last;
if (last >= first && last->GetType() == kIdentToken &&
EqualIgnoringASCIICase(last->Value(), "important")) {
--last;
while (last >= first && last->GetType() == kWhitespaceToken)
--last;
if (last >= first && last->GetType() == kDelimiterToken &&
last->Delimiter() == '!') {
tokenized_value.range = tokenized_value.range.MakeSubRange(first, last);
// Truncate the text to remove the delimiter and everything after it.
DCHECK_NE(tokenized_value.text.ToString().find('!'), kNotFound);
unsigned truncated_length = tokenized_value.text.length() - 1;
while (tokenized_value.text[truncated_length] != '!')
--truncated_length;
tokenized_value.text =
StringView(tokenized_value.text, 0, truncated_length);
return true;
}
}
return false;
}
std::unique_ptr<Vector<double>> CSSParserImpl::ConsumeKeyframeKeyList(
CSSParserTokenRange range) {
std::unique_ptr<Vector<double>> result = std::make_unique<Vector<double>>();
while (true) {
range.ConsumeWhitespace();
const CSSParserToken& token = range.ConsumeIncludingWhitespace();
if (token.GetType() == kPercentageToken && token.NumericValue() >= 0 &&
token.NumericValue() <= 100)
result->push_back(token.NumericValue() / 100);
else if (token.GetType() == kIdentToken &&
EqualIgnoringASCIICase(token.Value(), "from"))
result->push_back(0);
else if (token.GetType() == kIdentToken &&
EqualIgnoringASCIICase(token.Value(), "to"))
result->push_back(1);
else
return nullptr; // Parser error, invalid value in keyframe selector
if (range.AtEnd())
return result;
if (range.Consume().GetType() != kCommaToken)
return nullptr; // Parser error
}
}
} // namespace blink