| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/351564777): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "third_party/blink/renderer/core/css/parser/css_parser_impl.h" |
| |
| #include <bitset> |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/cpu.h" |
| #include "third_party/blink/renderer/core/animation/timeline_offset.h" |
| #include "third_party/blink/renderer/core/core_probes_inl.h" |
| #include "third_party/blink/renderer/core/css/css_custom_ident_value.h" |
| #include "third_party/blink/renderer/core/css/css_font_family_value.h" |
| #include "third_party/blink/renderer/core/css/css_keyframes_rule.h" |
| #include "third_party/blink/renderer/core/css/css_position_try_rule.h" |
| #include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h" |
| #include "third_party/blink/renderer/core/css/css_selector.h" |
| #include "third_party/blink/renderer/core/css/css_style_sheet.h" |
| #include "third_party/blink/renderer/core/css/css_syntax_string_parser.h" |
| #include "third_party/blink/renderer/core/css/css_unparsed_declaration_value.h" |
| #include "third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.h" |
| #include "third_party/blink/renderer/core/css/parser/container_query_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_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/find_length_of_declaration_list-inl.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_font_feature_values.h" |
| #include "third_party/blink/renderer/core/css/style_rule_font_palette_values.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_rule_nested_declarations.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/local_frame_ukm_aggregator.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" |
| #include "third_party/blink/renderer/platform/wtf/text/text_position.h" |
| |
| using std::swap; |
| |
| 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(); |
| } |
| |
| AtomicString result; |
| { |
| CSSParserTokenStream::BlockGuard guard(stream); |
| stream.ConsumeWhitespace(); |
| // If the block doesn't start with a quote, then the tokenizer |
| // would return a kUrlToken or kBadUrlToken instead of a |
| // kFunctionToken. Note also that this Peek() placates the |
| // DCHECK that we Peek() before Consume(). |
| DCHECK(stream.Peek().GetType() == kStringToken || |
| stream.Peek().GetType() == kBadStringToken) |
| << "Got unexpected token " << stream.Peek(); |
| const CSSParserToken& uri = stream.ConsumeIncludingWhitespace(); |
| if (uri.GetType() != kBadStringToken && stream.UncheckedAtEnd()) { |
| DCHECK_EQ(uri.GetType(), kStringToken); |
| result = uri.Value().ToAtomicString(); |
| } |
| } |
| stream.ConsumeWhitespace(); |
| return result; |
| } |
| |
| // Finds the longest prefix of |stream| that matches a <layer-name> and parses |
| // it. Returns an empty result with |stream| unmodified if parsing fails. |
| StyleRuleBase::LayerName ConsumeCascadeLayerName(CSSParserTokenStream& stream) { |
| CSSParserTokenStream::State savepoint = stream.Save(); |
| StyleRuleBase::LayerName name; |
| while (!stream.AtEnd() && stream.Peek().GetType() == kIdentToken) { |
| const CSSParserToken& name_part = stream.Consume(); |
| name.emplace_back(name_part.Value().ToString()); |
| |
| // Check if we have a next part. |
| if (stream.Peek().GetType() != kDelimiterToken || |
| stream.Peek().Delimiter() != '.') { |
| break; |
| } |
| CSSParserTokenStream::State inner_savepoint = stream.Save(); |
| stream.Consume(); |
| if (stream.Peek().GetType() != kIdentToken) { |
| stream.Restore(inner_savepoint); |
| break; |
| } |
| } |
| |
| if (!name.size()) { |
| stream.Restore(savepoint); |
| } else { |
| stream.ConsumeWhitespace(); |
| } |
| |
| return name; |
| } |
| |
| StyleRule::RuleType RuleTypeForMutableDeclaration( |
| MutableCSSPropertyValueSet* declaration) { |
| switch (declaration->CssParserMode()) { |
| case kCSSFontFaceRuleMode: |
| return StyleRule::kFontFace; |
| case kCSSKeyframeRuleMode: |
| return StyleRule::kKeyframe; |
| case kCSSPropertyRuleMode: |
| return StyleRule::kProperty; |
| case kCSSFontPaletteValuesRuleMode: |
| return StyleRule::kFontPaletteValues; |
| case kCSSPositionTryRuleMode: |
| return StyleRule::kPositionTry; |
| default: |
| return StyleRule::kStyle; |
| } |
| } |
| |
| std::optional<StyleRuleFontFeature::FeatureType> ToStyleRuleFontFeatureType( |
| CSSAtRuleID rule_id) { |
| switch (rule_id) { |
| case CSSAtRuleID::kCSSAtRuleStylistic: |
| return StyleRuleFontFeature::FeatureType::kStylistic; |
| case CSSAtRuleID::kCSSAtRuleStyleset: |
| return StyleRuleFontFeature::FeatureType::kStyleset; |
| case CSSAtRuleID::kCSSAtRuleCharacterVariant: |
| return StyleRuleFontFeature::FeatureType::kCharacterVariant; |
| case CSSAtRuleID::kCSSAtRuleSwash: |
| return StyleRuleFontFeature::FeatureType::kSwash; |
| case CSSAtRuleID::kCSSAtRuleOrnaments: |
| return StyleRuleFontFeature::FeatureType::kOrnaments; |
| case CSSAtRuleID::kCSSAtRuleAnnotation: |
| return StyleRuleFontFeature::FeatureType::kAnnotation; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| } // 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, |
| StringView string, |
| bool important, |
| const CSSParserContext* context) { |
| STACK_UNINITIALIZED CSSParserImpl parser(context); |
| StyleRule::RuleType rule_type = RuleTypeForMutableDeclaration(declaration); |
| CSSParserTokenStream stream(string); |
| parser.ConsumeDeclarationValue(stream, unresolved_property, |
| /*is_in_declaration_list=*/false, rule_type); |
| if (parser.parsed_properties_.empty()) { |
| return MutableCSSPropertyValueSet::kParseError; |
| } |
| if (important) { |
| for (CSSPropertyValue& property : parser.parsed_properties_) { |
| property.SetImportant(); |
| } |
| } |
| return declaration->AddParsedProperties(parser.parsed_properties_); |
| } |
| |
| MutableCSSPropertyValueSet::SetResult CSSParserImpl::ParseVariableValue( |
| MutableCSSPropertyValueSet* declaration, |
| const AtomicString& property_name, |
| StringView value, |
| bool important, |
| const CSSParserContext* context, |
| bool is_animation_tainted) { |
| STACK_UNINITIALIZED CSSParserImpl parser(context); |
| CSSParserTokenStream stream(value); |
| if (!parser.ConsumeVariableValue(stream, property_name, |
| /*allow_important_annotation=*/false, |
| is_animation_tainted)) { |
| return MutableCSSPropertyValueSet::kParseError; |
| } |
| if (important) { |
| parser.parsed_properties_.back().SetImportant(); |
| } |
| return declaration->AddParsedProperties(parser.parsed_properties_); |
| } |
| |
| static inline void FilterProperties( |
| bool important, |
| const HeapVector<CSSPropertyValue, 64>& input, |
| HeapVector<CSSPropertyValue, 64>& 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 = property.CustomPropertyName(); |
| 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, 64>& parsed_properties, |
| CSSParserMode mode, |
| const Document* document) { |
| if (mode != kHTMLQuirksMode && |
| (parsed_properties.size() < 2 || |
| (parsed_properties.size() == 2 && |
| parsed_properties[0].Id() != parsed_properties[1].Id()))) { |
| // Fast path for the situations where we can trivially detect that there can |
| // be no collision between properties, and don't need to reorder, make |
| // bitsets, or similar. |
| ImmutableCSSPropertyValueSet* result = |
| ImmutableCSSPropertyValueSet::Create(parsed_properties, mode); |
| parsed_properties.clear(); |
| return result; |
| } |
| |
| std::bitset<kNumCSSProperties> seen_properties; |
| wtf_size_t unused_entries = parsed_properties.size(); |
| HeapVector<CSSPropertyValue, 64> 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); |
| |
| bool count_cursor_hand = false; |
| if (document && mode == kHTMLQuirksMode && |
| seen_properties.test(GetCSSPropertyIDIndex(CSSPropertyID::kCursor))) { |
| // See if the properties contain “cursor: hand” without also containing |
| // “cursor: pointer”. This is a reasonable approximation for whether |
| // removing support for the former would actually matter. (Of course, |
| // we don't check whether “cursor: hand” could lose in the cascade |
| // due to properties coming from other declarations, but that would be |
| // much more complicated) |
| bool contains_cursor_hand = false; |
| bool contains_cursor_pointer = false; |
| for (const CSSPropertyValue& property : parsed_properties) { |
| const CSSIdentifierValue* value = |
| DynamicTo<CSSIdentifierValue>(property.Value()); |
| if (value) { |
| if (value->WasQuirky()) { |
| contains_cursor_hand = true; |
| } else if (value->GetValueID() == CSSValueID::kPointer) { |
| contains_cursor_pointer = true; |
| } |
| } |
| } |
| if (contains_cursor_hand && !contains_cursor_pointer) { |
| document->CountUse(WebFeature::kQuirksModeCursorHand); |
| count_cursor_hand = true; |
| } |
| } |
| |
| ImmutableCSSPropertyValueSet* result = ImmutableCSSPropertyValueSet::Create( |
| base::span(results).subspan(unused_entries), mode, count_cursor_hand); |
| 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()); |
| CSSParserTokenStream stream(string); |
| parser.ConsumeDeclarationList(stream, StyleRule::kStyle, |
| CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| return CreateCSSPropertyValueSet(parser.parsed_properties_, mode, &document); |
| } |
| |
| ImmutableCSSPropertyValueSet* CSSParserImpl::ParseInlineStyleDeclaration( |
| const String& string, |
| CSSParserMode parser_mode, |
| SecureContextMode secure_context_mode, |
| const Document* document) { |
| auto* context = |
| MakeGarbageCollected<CSSParserContext>(parser_mode, secure_context_mode); |
| CSSParserImpl parser(context); |
| CSSParserTokenStream stream(string); |
| parser.ConsumeDeclarationList(stream, StyleRule::kStyle, |
| CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| return CreateCSSPropertyValueSet(parser.parsed_properties_, parser_mode, |
| document); |
| } |
| |
| bool CSSParserImpl::ParseDeclarationList( |
| MutableCSSPropertyValueSet* declaration, |
| const String& string, |
| const CSSParserContext* context) { |
| CSSParserImpl parser(context); |
| StyleRule::RuleType rule_type = RuleTypeForMutableDeclaration(declaration); |
| CSSParserTokenStream stream(string); |
| // See function declaration comment for why parent_rule_for_nesting == |
| // nullptr. |
| parser.ConsumeDeclarationList(stream, rule_type, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| if (parser.parsed_properties_.empty()) { |
| return false; |
| } |
| |
| std::bitset<kNumCSSProperties> seen_properties; |
| wtf_size_t unused_entries = parser.parsed_properties_.size(); |
| HeapVector<CSSPropertyValue, 64> 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::ParseNestedDeclarationsRule( |
| const CSSParserContext* context, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| StringView text) { |
| CSSParserImpl parser(context); |
| CSSParserTokenStream stream(text); |
| |
| HeapVector<Member<StyleRuleBase>, 4> child_rules; |
| |
| // Using nested_declarations_start_index=0u causes the leading block |
| // of declarations (the only block) to be wrapped in a CSSNestedDeclarations |
| // rule. |
| // |
| // See comment above CSSParserImpl::ConsumeDeclarationList (definition) |
| // for more on nested_declarations_start_index. |
| parser.ConsumeDeclarationList(stream, StyleRule::RuleType::kStyle, |
| nesting_type, parent_rule_for_nesting, |
| /*nested_declarations_start_index=*/0u, |
| &child_rules); |
| |
| return child_rules.size() == 1u ? child_rules.back().Get() : nullptr; |
| } |
| |
| StyleRuleBase* CSSParserImpl::ParseRule(const String& string, |
| const CSSParserContext* context, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| StyleSheetContents* style_sheet, |
| AllowedRulesType allowed_rules) { |
| CSSParserImpl parser(context, style_sheet); |
| CSSParserTokenStream stream(string); |
| stream.ConsumeWhitespace(); |
| if (stream.UncheckedAtEnd()) { |
| return nullptr; // Parse error, empty rule |
| } |
| StyleRuleBase* rule; |
| if (stream.UncheckedPeek().GetType() == kAtKeywordToken) { |
| rule = parser.ConsumeAtRule(stream, allowed_rules, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr); |
| } else if (allowed_rules == kPageMarginRules) { |
| // Style rules are not allowed inside @page. |
| rule = nullptr; |
| } else { |
| rule = parser.ConsumeQualifiedRule(stream, allowed_rules, nesting_type, |
| parent_rule_for_nesting); |
| } |
| 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) { |
| std::optional<LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer> timer; |
| if (context->GetDocument() && context->GetDocument()->View()) { |
| if (auto* metrics_aggregator = |
| context->GetDocument()->View()->GetUkmAggregator()) { |
| timer.emplace(metrics_aggregator->GetScopedTimer( |
| static_cast<size_t>(LocalFrameUkmAggregator::kParseStyleSheet))); |
| } |
| } |
| 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"); |
| CSSParserTokenStream stream(string); |
| 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, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| [&style_sheet, &result, &string, allow_import_rules, context]( |
| StyleRuleBase* rule, wtf_size_t offset) { |
| if (rule->IsCharsetRule()) { |
| return; |
| } |
| if (rule->IsImportRule()) { |
| if (!allow_import_rules || context->IsForMarkupSanitization()) { |
| result = ParseSheetResult::kHasUnallowedImportRule; |
| return; |
| } |
| |
| Document* document = style_sheet->AnyOwnerDocument(); |
| if (document) { |
| TextPosition position = TextPosition::MinimumPosition(); |
| probe::GetTextPosition(document, offset, &string, &position); |
| To<StyleRuleImport>(rule)->SetPositionHint(position); |
| } |
| } |
| |
| 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", stream.TokenCount(), "length", |
| string.length()); |
| return result; |
| } |
| |
| // static |
| CSSSelectorList* CSSParserImpl::ParsePageSelector( |
| CSSParserTokenStream& stream, |
| StyleSheetContents* style_sheet, |
| const CSSParserContext& context) { |
| // We only support a small subset of the css-page spec. |
| stream.ConsumeWhitespace(); |
| AtomicString type_selector; |
| if (stream.Peek().GetType() == kIdentToken) { |
| type_selector = stream.Consume().Value().ToAtomicString(); |
| } |
| |
| AtomicString pseudo; |
| if (stream.Peek().GetType() == kColonToken) { |
| stream.Consume(); |
| if (stream.Peek().GetType() != kIdentToken) { |
| return nullptr; |
| } |
| pseudo = stream.Consume().Value().ToAtomicString(); |
| } |
| |
| stream.ConsumeWhitespace(); |
| |
| HeapVector<CSSSelector> selectors; |
| if (!type_selector.IsNull()) { |
| selectors.push_back( |
| CSSSelector(QualifiedName(g_null_atom, type_selector, g_star_atom))); |
| } |
| if (!pseudo.IsNull()) { |
| CSSSelector selector; |
| selector.SetMatch(CSSSelector::kPagePseudoClass); |
| selector.UpdatePseudoPage(pseudo.LowerASCII(), context.GetDocument()); |
| if (selector.GetPseudoType() == CSSSelector::kPseudoUnknown) { |
| return nullptr; |
| } |
| if (selectors.size() != 0) { |
| selectors[0].SetLastInComplexSelector(false); |
| } |
| selectors.push_back(selector); |
| } |
| if (selectors.empty()) { |
| selectors.push_back(CSSSelector()); |
| } |
| selectors[0].SetForPage(); |
| selectors.back().SetLastInComplexSelector(true); |
| return CSSSelectorList::AdoptSelectorVector( |
| base::span<CSSSelector>(selectors)); |
| } |
| |
| std::unique_ptr<Vector<KeyframeOffset>> CSSParserImpl::ParseKeyframeKeyList( |
| const CSSParserContext* context, |
| const String& key_list) { |
| CSSParserTokenStream stream(key_list); |
| std::unique_ptr<Vector<KeyframeOffset>> result = |
| ConsumeKeyframeKeyList(context, stream); |
| if (stream.AtEnd()) { |
| return result; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| String CSSParserImpl::ParseCustomPropertyName(StringView name_text) { |
| CSSParserTokenStream stream(name_text); |
| const CSSParserToken name_token = stream.Peek(); |
| if (!CSSVariableParser::IsValidVariableName(name_token)) { |
| return {}; |
| } |
| stream.ConsumeIncludingWhitespace(); |
| if (!stream.AtEnd()) { |
| return {}; |
| } |
| return name_token.Value().ToString(); |
| } |
| |
| bool CSSParserImpl::ConsumeSupportsDeclaration(CSSParserTokenStream& stream) { |
| DCHECK(parsed_properties_.empty()); |
| // Even though we might use an observer here, this is just to test if we |
| // successfully parse the stream, so we can temporarily remove the observer. |
| CSSParserObserver* observer_copy = observer_; |
| observer_ = nullptr; |
| ConsumeDeclaration(stream, StyleRule::kStyle); |
| observer_ = observer_copy; |
| |
| bool result = !parsed_properties_.empty(); |
| parsed_properties_.clear(); |
| return result; |
| } |
| |
| void CSSParserImpl::ParseDeclarationListForInspector( |
| const String& declaration, |
| const CSSParserContext* context, |
| CSSParserObserver& observer) { |
| CSSParserImpl parser(context); |
| parser.observer_ = &observer; |
| observer.StartRuleHeader(StyleRule::kStyle, 0); |
| observer.EndRuleHeader(1); |
| CSSParserTokenStream stream(declaration); |
| observer.StartRuleBody(stream.Offset()); |
| parser.ConsumeDeclarationList(stream, StyleRule::kStyle, |
| CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| observer.EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| void CSSParserImpl::ParseStyleSheetForInspector(const String& string, |
| const CSSParserContext* context, |
| StyleSheetContents* style_sheet, |
| CSSParserObserver& observer) { |
| CSSParserImpl parser(context, style_sheet); |
| parser.observer_ = &observer; |
| CSSParserTokenStream stream(string); |
| bool first_rule_valid = |
| parser.ConsumeRuleList(stream, kTopLevelRuleList, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| [&style_sheet](StyleRuleBase* rule, wtf_size_t) { |
| 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) { |
| // NOTE: Lazy parsing does not support nested rules (it happens |
| // only after matching, which means that we cannot insert child rules |
| // we encounter during parsing -- we never match against them), |
| // so parent_rule_for_nesting is always nullptr here. The parser |
| // explicitly makes sure we do not invoke lazy parsing for rules |
| // with child rules in them. |
| CSSParserTokenStream stream(string, offset); |
| CSSParserTokenStream::BlockGuard guard(stream); |
| CSSParserImpl parser(context); |
| parser.ConsumeDeclarationList(stream, StyleRule::kStyle, |
| CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| return CreateCSSPropertyValueSet(parser.parsed_properties_, context->Mode(), |
| context->GetDocument()); |
| } |
| |
| 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()) { |
| return CSSParserImpl::kAllowLayerStatementRules; |
| } |
| if (rule->IsLayerStatementRule()) { |
| if (allowed_rules <= CSSParserImpl::kAllowLayerStatementRules) { |
| return CSSParserImpl::kAllowLayerStatementRules; |
| } |
| return CSSParserImpl::kRegularRules; |
| } |
| if (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, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| 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()) { |
| wtf_size_t offset = stream.Offset(); |
| StyleRuleBase* rule = nullptr; |
| switch (stream.UncheckedPeek().GetType()) { |
| case kWhitespaceToken: |
| stream.UncheckedConsume(); |
| continue; |
| case kAtKeywordToken: |
| rule = ConsumeAtRule(stream, allowed_rules, nesting_type, |
| parent_rule_for_nesting); |
| break; |
| case kCDOToken: |
| case kCDCToken: |
| if (rule_list_type == kTopLevelRuleList) { |
| stream.UncheckedConsume(); |
| continue; |
| } |
| [[fallthrough]]; |
| default: |
| rule = ConsumeQualifiedRule(stream, allowed_rules, nesting_type, |
| parent_rule_for_nesting); |
| break; |
| } |
| if (!seen_rule) { |
| seen_rule = true; |
| first_rule_valid = rule; |
| } |
| if (rule) { |
| allowed_rules = ComputeNewAllowedRules(allowed_rules, rule); |
| callback(rule, offset); |
| } |
| DCHECK_GT(stream.Offset(), offset); |
| } |
| |
| return first_rule_valid; |
| } |
| |
| // Same as ConsumeEndOfPreludeForAtRuleWithBlock() below, but for at-rules |
| // that don't have a block and are terminated only by semicolon. |
| bool CSSParserImpl::ConsumeEndOfPreludeForAtRuleWithoutBlock( |
| CSSParserTokenStream& stream, |
| CSSAtRuleID id) { |
| stream.ConsumeWhitespace(); |
| if (stream.AtEnd()) { |
| return true; |
| } |
| if (stream.UncheckedPeek().GetType() == kSemicolonToken) { |
| stream.UncheckedConsume(); // kSemicolonToken |
| return true; |
| } |
| |
| if (observer_) { |
| observer_->ObserveErroneousAtRule(stream.Offset(), id); |
| } |
| |
| // Consume the erroneous block. |
| ConsumeErroneousAtRule(stream, id); |
| return false; // Parse error, we expected no block. |
| } |
| |
| // Call this after parsing the prelude of an at-rule that takes a block |
| // (i.e. @foo-rule <prelude> /* call here */ { ... }). It will check |
| // that there is no junk after the prelude, and that there is indeed |
| // a block starting. If either of these are false, then it will consume |
| // until the end of the declaration (any junk after the prelude, |
| // and the block if one exists), notify the observer, and return false. |
| bool CSSParserImpl::ConsumeEndOfPreludeForAtRuleWithBlock( |
| CSSParserTokenStream& stream, |
| CSSAtRuleID id) { |
| stream.ConsumeWhitespace(); |
| |
| if (stream.AtEnd()) { |
| // Parse error, we expected a block. |
| if (observer_) { |
| observer_->ObserveErroneousAtRule(stream.Offset(), id); |
| } |
| return false; |
| } |
| if (stream.UncheckedPeek().GetType() == kLeftBraceToken) { |
| return true; |
| } |
| |
| // We have a parse error, so we need to return an error, but before that, |
| // we need to consume until the end of the declaration. |
| ConsumeErroneousAtRule(stream, id); |
| return false; |
| } |
| |
| void CSSParserImpl::ConsumeErroneousAtRule(CSSParserTokenStream& stream, |
| CSSAtRuleID id) { |
| if (observer_) { |
| observer_->ObserveErroneousAtRule(stream.Offset(), id); |
| } |
| // Consume the prelude and block if present. |
| stream.SkipUntilPeekedTypeIs<kLeftBraceToken, kSemicolonToken>(); |
| if (!stream.AtEnd()) { |
| if (stream.UncheckedPeek().GetType() == kLeftBraceToken) { |
| CSSParserTokenStream::BlockGuard guard(stream); |
| } else { |
| stream.UncheckedConsume(); // kSemicolonToken |
| } |
| } |
| } |
| |
| StyleRuleBase* CSSParserImpl::ConsumeAtRule( |
| CSSParserTokenStream& stream, |
| AllowedRulesType allowed_rules, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| DCHECK_EQ(stream.Peek().GetType(), kAtKeywordToken); |
| CSSParserToken name_token = |
| stream.ConsumeIncludingWhitespace(); // Must live until CssAtRuleID(). |
| const StringView name = name_token.Value(); |
| const CSSAtRuleID id = CssAtRuleID(name); |
| return ConsumeAtRuleContents(id, stream, allowed_rules, nesting_type, |
| parent_rule_for_nesting); |
| } |
| |
| StyleRuleBase* CSSParserImpl::ConsumeAtRuleContents( |
| CSSAtRuleID id, |
| CSSParserTokenStream& stream, |
| AllowedRulesType allowed_rules, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| if (allowed_rules == kNestedGroupRules) { |
| if (id != CSSAtRuleID::kCSSAtRuleMedia && // [css-conditional-3] |
| id != CSSAtRuleID::kCSSAtRuleSupports && // [css-conditional-3] |
| id != CSSAtRuleID::kCSSAtRuleContainer && // [css-contain-3] |
| id != CSSAtRuleID::kCSSAtRuleLayer && // [css-cascade-5] |
| id != CSSAtRuleID::kCSSAtRuleScope && // [css-cascade-6] |
| id != CSSAtRuleID::kCSSAtRuleStartingStyle && |
| id != CSSAtRuleID::kCSSAtRuleViewTransition && |
| id != CSSAtRuleID::kCSSAtRuleApplyMixin && |
| (id < CSSAtRuleID::kCSSAtRuleTopLeftCorner || |
| id > CSSAtRuleID::kCSSAtRuleRightBottom)) { |
| ConsumeErroneousAtRule(stream, id); |
| return nullptr; |
| } |
| allowed_rules = kRegularRules; |
| } |
| |
| // @import rules have a URI component that is not technically part of the |
| // prelude. |
| AtomicString import_prelude_uri; |
| if (allowed_rules <= kAllowImportRules && |
| id == CSSAtRuleID::kCSSAtRuleImport) { |
| import_prelude_uri = ConsumeStringOrURI(stream); |
| } |
| |
| if (id != CSSAtRuleID::kCSSAtRuleInvalid && |
| context_->IsUseCounterRecordingEnabled()) { |
| CountAtRule(context_, id); |
| } |
| |
| if (allowed_rules == kKeyframeRules || allowed_rules == kNoRules) { |
| // Parse error, no at-rules supported inside @keyframes, |
| // or blocks supported inside declaration lists. |
| ConsumeErroneousAtRule(stream, id); |
| return nullptr; |
| } |
| |
| stream.EnsureLookAhead(); |
| if (allowed_rules == kAllowCharsetRules && |
| id == CSSAtRuleID::kCSSAtRuleCharset) { |
| return ConsumeCharsetRule(stream); |
| } else if (allowed_rules <= kAllowImportRules && |
| id == CSSAtRuleID::kCSSAtRuleImport) { |
| return ConsumeImportRule(std::move(import_prelude_uri), stream); |
| } else if (allowed_rules <= kAllowNamespaceRules && |
| id == CSSAtRuleID::kCSSAtRuleNamespace) { |
| return ConsumeNamespaceRule(stream); |
| } else if (allowed_rules == kFontFeatureRules) { |
| if (id == CSSAtRuleID::kCSSAtRuleStylistic || |
| id == CSSAtRuleID::kCSSAtRuleStyleset || |
| id == CSSAtRuleID::kCSSAtRuleCharacterVariant || |
| id == CSSAtRuleID::kCSSAtRuleSwash || |
| id == CSSAtRuleID::kCSSAtRuleOrnaments || |
| id == CSSAtRuleID::kCSSAtRuleAnnotation) { |
| return ConsumeFontFeatureRule(id, stream); |
| } else { |
| return nullptr; |
| } |
| } else if (allowed_rules == kPageMarginRules) { |
| if (id < CSSAtRuleID::kCSSAtRuleTopLeftCorner || |
| id > CSSAtRuleID::kCSSAtRuleRightBottom) { |
| ConsumeErroneousAtRule(stream, id); |
| return nullptr; |
| } |
| return ConsumePageMarginRule(id, stream); |
| } else { |
| DCHECK_LE(allowed_rules, kRegularRules); |
| |
| switch (id) { |
| case CSSAtRuleID::kCSSAtRuleViewTransition: |
| return ConsumeViewTransitionRule(stream); |
| case CSSAtRuleID::kCSSAtRuleContainer: |
| return ConsumeContainerRule(stream, nesting_type, |
| parent_rule_for_nesting); |
| case CSSAtRuleID::kCSSAtRuleMedia: |
| return ConsumeMediaRule(stream, nesting_type, parent_rule_for_nesting); |
| case CSSAtRuleID::kCSSAtRuleSupports: |
| return ConsumeSupportsRule(stream, nesting_type, |
| parent_rule_for_nesting); |
| case CSSAtRuleID::kCSSAtRuleStartingStyle: |
| return ConsumeStartingStyleRule(stream, nesting_type, |
| parent_rule_for_nesting); |
| case CSSAtRuleID::kCSSAtRuleFontFace: |
| return ConsumeFontFaceRule(stream); |
| case CSSAtRuleID::kCSSAtRuleFontPaletteValues: |
| return ConsumeFontPaletteValuesRule(stream); |
| case CSSAtRuleID::kCSSAtRuleFontFeatureValues: |
| return ConsumeFontFeatureValuesRule(stream); |
| case CSSAtRuleID::kCSSAtRuleWebkitKeyframes: |
| return ConsumeKeyframesRule(true, stream); |
| case CSSAtRuleID::kCSSAtRuleKeyframes: |
| return ConsumeKeyframesRule(false, stream); |
| case CSSAtRuleID::kCSSAtRuleLayer: |
| return ConsumeLayerRule(stream, nesting_type, parent_rule_for_nesting); |
| case CSSAtRuleID::kCSSAtRulePage: |
| return ConsumePageRule(stream); |
| case CSSAtRuleID::kCSSAtRuleProperty: |
| return ConsumePropertyRule(stream); |
| case CSSAtRuleID::kCSSAtRuleScope: |
| return ConsumeScopeRule(stream, nesting_type, parent_rule_for_nesting); |
| case CSSAtRuleID::kCSSAtRuleCounterStyle: |
| return ConsumeCounterStyleRule(stream); |
| case CSSAtRuleID::kCSSAtRuleFunction: |
| return ConsumeFunctionRule(stream); |
| case CSSAtRuleID::kCSSAtRuleMixin: |
| return ConsumeMixinRule(stream); |
| case CSSAtRuleID::kCSSAtRuleApplyMixin: |
| return ConsumeApplyMixinRule(stream); |
| case CSSAtRuleID::kCSSAtRulePositionTry: |
| return ConsumePositionTryRule(stream); |
| case CSSAtRuleID::kCSSAtRuleInvalid: |
| case CSSAtRuleID::kCSSAtRuleCharset: |
| case CSSAtRuleID::kCSSAtRuleImport: |
| case CSSAtRuleID::kCSSAtRuleNamespace: |
| case CSSAtRuleID::kCSSAtRuleStylistic: |
| case CSSAtRuleID::kCSSAtRuleStyleset: |
| case CSSAtRuleID::kCSSAtRuleCharacterVariant: |
| case CSSAtRuleID::kCSSAtRuleSwash: |
| case CSSAtRuleID::kCSSAtRuleOrnaments: |
| case CSSAtRuleID::kCSSAtRuleAnnotation: |
| case CSSAtRuleID::kCSSAtRuleTopLeftCorner: |
| case CSSAtRuleID::kCSSAtRuleTopLeft: |
| case CSSAtRuleID::kCSSAtRuleTopCenter: |
| case CSSAtRuleID::kCSSAtRuleTopRight: |
| case CSSAtRuleID::kCSSAtRuleTopRightCorner: |
| case CSSAtRuleID::kCSSAtRuleBottomLeftCorner: |
| case CSSAtRuleID::kCSSAtRuleBottomLeft: |
| case CSSAtRuleID::kCSSAtRuleBottomCenter: |
| case CSSAtRuleID::kCSSAtRuleBottomRight: |
| case CSSAtRuleID::kCSSAtRuleBottomRightCorner: |
| case CSSAtRuleID::kCSSAtRuleLeftTop: |
| case CSSAtRuleID::kCSSAtRuleLeftMiddle: |
| case CSSAtRuleID::kCSSAtRuleLeftBottom: |
| case CSSAtRuleID::kCSSAtRuleRightTop: |
| case CSSAtRuleID::kCSSAtRuleRightMiddle: |
| case CSSAtRuleID::kCSSAtRuleRightBottom: |
| ConsumeErroneousAtRule(stream, id); |
| return nullptr; // Parse error, unrecognised or not-allowed at-rule |
| } |
| } |
| } |
| |
| StyleRuleBase* CSSParserImpl::ConsumeQualifiedRule( |
| CSSParserTokenStream& stream, |
| AllowedRulesType allowed_rules, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| if (allowed_rules <= kRegularRules) { |
| bool invalid_rule_error_ignored = false; // Only relevant when nested. |
| return ConsumeStyleRule(stream, nesting_type, parent_rule_for_nesting, |
| /* nested */ false, invalid_rule_error_ignored); |
| } |
| |
| if (allowed_rules == kKeyframeRules) { |
| stream.EnsureLookAhead(); |
| const wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| std::unique_ptr<Vector<KeyframeOffset>> key_list = |
| ConsumeKeyframeKeyList(context_, stream); |
| stream.ConsumeWhitespace(); |
| const RangeOffset prelude_offset(prelude_offset_start, |
| stream.LookAheadOffset()); |
| |
| if (stream.Peek().GetType() != kLeftBraceToken) { |
| key_list = nullptr; // Parse error, junk after prelude |
| stream.SkipUntilPeekedTypeIs<kLeftBraceToken>(); |
| } |
| if (stream.AtEnd()) { |
| return nullptr; // Parse error, EOF instead of qualified rule block |
| } |
| |
| CSSParserTokenStream::BlockGuard guard(stream); |
| return ConsumeKeyframeStyleRule(std::move(key_list), prelude_offset, |
| stream); |
| } |
| if (allowed_rules == kFontFeatureRules) { |
| // We get here if something other than an at rule (e.g. @swash, |
| // @ornaments... ) was found within @font-feature-values. As we don't |
| // support font-display in @font-feature-values, we try to it by scanning |
| // until the at-rule or until the block may end. Compare |
| // https://drafts.csswg.org/css-fonts-4/#ex-invalid-ignored |
| stream.EnsureLookAhead(); |
| stream.SkipUntilPeekedTypeIs<kAtKeywordToken>(); |
| return nullptr; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| StyleRulePageMargin* CSSParserImpl::ConsumePageMarginRule( |
| CSSAtRuleID rule_id, |
| CSSParserTokenStream& stream) { |
| wtf_size_t header_start = stream.LookAheadOffset(); |
| // NOTE: @page-margin prelude should be empty. |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, rule_id)) { |
| return nullptr; |
| } |
| wtf_size_t header_end = stream.LookAheadOffset(); |
| |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kPageMargin, header_start); |
| observer_->EndRuleHeader(header_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| ConsumeDeclarationList(stream, StyleRule::kPageMargin, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| return MakeGarbageCollected<StyleRulePageMargin>( |
| rule_id, CreateCSSPropertyValueSet(parsed_properties_, context_->Mode(), |
| context_->GetDocument())); |
| } |
| |
| StyleRuleCharset* CSSParserImpl::ConsumeCharsetRule( |
| CSSParserTokenStream& stream) { |
| const CSSParserToken& string = stream.Peek(); |
| if (string.GetType() != kStringToken || !stream.AtEnd()) { |
| // Parse error, expected a single string. |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleCharset); |
| return nullptr; |
| } |
| stream.ConsumeIncludingWhitespace(); |
| if (!ConsumeEndOfPreludeForAtRuleWithoutBlock( |
| stream, CSSAtRuleID::kCSSAtRuleCharset)) { |
| return nullptr; |
| } |
| |
| return MakeGarbageCollected<StyleRuleCharset>(); |
| } |
| |
| StyleRuleImport* CSSParserImpl::ConsumeImportRule( |
| const AtomicString& uri, |
| CSSParserTokenStream& stream) { |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| |
| if (uri.IsNull()) { |
| // Parse error, expected string or URI. |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleImport); |
| return nullptr; |
| } |
| |
| StyleRuleBase::LayerName layer; |
| if (stream.Peek().GetType() == kIdentToken && |
| stream.Peek().Id() == CSSValueID::kLayer) { |
| stream.ConsumeIncludingWhitespace(); |
| layer = StyleRuleBase::LayerName({g_empty_atom}); |
| } else if (stream.Peek().GetType() == kFunctionToken && |
| stream.Peek().FunctionId() == CSSValueID::kLayer) { |
| CSSParserTokenStream::RestoringBlockGuard guard(stream); |
| stream.ConsumeWhitespace(); |
| StyleRuleBase::LayerName name = ConsumeCascadeLayerName(stream); |
| if (name.size() && stream.AtEnd()) { |
| layer = std::move(name); |
| guard.Release(); |
| } else { |
| // Invalid layer() function can still be parsed as <general-enclosed> |
| } |
| } |
| if (layer.size()) { |
| context_->Count(WebFeature::kCSSCascadeLayers); |
| } |
| |
| stream.ConsumeWhitespace(); |
| |
| // https://drafts.csswg.org/css-cascade-5/#at-import |
| // |
| // <import-conditions> = |
| // [ supports([ <supports-condition> | <declaration> ]) ]? |
| // <media-query-list>? |
| StringView supports_string = g_null_atom; |
| CSSSupportsParser::Result supported = CSSSupportsParser::Result::kSupported; |
| if (RuntimeEnabledFeatures::CSSSupportsForImportRulesEnabled() && |
| stream.Peek().GetType() == kFunctionToken && |
| stream.Peek().FunctionId() == CSSValueID::kSupports) { |
| { |
| CSSParserTokenStream::BlockGuard guard(stream); |
| stream.ConsumeWhitespace(); |
| wtf_size_t supports_offset_start = stream.Offset(); |
| |
| // First, try parsing as <declaration>. |
| CSSParserTokenStream::State savepoint = stream.Save(); |
| if (stream.Peek().GetType() == kIdentToken && |
| CSSParserImpl::ConsumeSupportsDeclaration(stream)) { |
| supported = CSSSupportsParser::Result::kSupported; |
| } else { |
| // Rewind and try parsing as <supports-condition>. |
| stream.Restore(savepoint); |
| supported = CSSSupportsParser::ConsumeSupportsCondition(stream, *this); |
| } |
| wtf_size_t supports_offset_end = stream.Offset(); |
| supports_string = stream.StringRangeAt( |
| supports_offset_start, supports_offset_end - supports_offset_start); |
| } |
| if (supported == CSSSupportsParser::Result::kParseFailure) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleImport); |
| return nullptr; |
| } |
| } |
| stream.ConsumeWhitespace(); |
| |
| // Parse the rest of the prelude as a media query. |
| // TODO(sesse): When the media query parser becomes streaming, |
| // we can just parse media queries here instead. |
| wtf_size_t media_query_offset_start = stream.Offset(); |
| stream.SkipUntilPeekedTypeIs<kLeftBraceToken, kSemicolonToken>(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| StringView media_query_string = stream.StringRangeAt( |
| media_query_offset_start, prelude_offset_end - media_query_offset_start); |
| |
| MediaQuerySet* media_query_set = MediaQueryParser::ParseMediaQuerySet( |
| media_query_string.ToString(), context_->GetExecutionContext()); |
| |
| if (!ConsumeEndOfPreludeForAtRuleWithoutBlock( |
| stream, CSSAtRuleID::kCSSAtRuleImport)) { |
| return nullptr; |
| } |
| |
| 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, std::move(layer), supported == CSSSupportsParser::Result::kSupported, |
| supports_string.ToString(), media_query_set, |
| context_->IsOriginClean() ? OriginClean::kTrue : OriginClean::kFalse); |
| } |
| |
| StyleRuleNamespace* CSSParserImpl::ConsumeNamespaceRule( |
| CSSParserTokenStream& stream) { |
| AtomicString namespace_prefix; |
| if (stream.Peek().GetType() == kIdentToken) { |
| namespace_prefix = |
| stream.ConsumeIncludingWhitespace().Value().ToAtomicString(); |
| } |
| |
| AtomicString uri(ConsumeStringOrURI(stream)); |
| if (uri.IsNull()) { |
| // Parse error, expected string or URI. |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleNamespace); |
| return nullptr; |
| } |
| if (!ConsumeEndOfPreludeForAtRuleWithoutBlock( |
| stream, CSSAtRuleID::kCSSAtRuleNamespace)) { |
| return nullptr; |
| } |
| |
| return MakeGarbageCollected<StyleRuleNamespace>(namespace_prefix, uri); |
| } |
| |
| StyleRule* CSSParserImpl::CreateImplicitNestedRule( |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| DCHECK(!RuntimeEnabledFeatures::CSSNestedDeclarationsEnabled()); |
| |
| constexpr bool kNotImplicit = |
| false; // The rule is implicit, but the &/:scope is not. |
| |
| HeapVector<CSSSelector, 2> selectors; |
| |
| switch (nesting_type) { |
| case CSSNestingType::kNone: |
| NOTREACHED(); |
| case CSSNestingType::kNesting: |
| // kPseudoParent |
| selectors.push_back(CSSSelector(parent_rule_for_nesting, kNotImplicit)); |
| break; |
| case CSSNestingType::kScope: { |
| // See CSSSelector::RelationType::kScopeActivation. |
| CSSSelector selector; |
| selector.SetTrue(); |
| selector.SetRelation(CSSSelector::kScopeActivation); |
| selectors.push_back(selector); |
| selectors.push_back(CSSSelector(AtomicString("scope"), kNotImplicit)); |
| break; |
| } |
| } |
| |
| CHECK(!selectors.empty()); |
| selectors.back().SetLastInComplexSelector(true); |
| selectors.back().SetLastInSelectorList(true); |
| |
| return StyleRule::Create( |
| base::span<CSSSelector>{selectors.data(), selectors.size()}, |
| CreateCSSPropertyValueSet(parsed_properties_, context_->Mode(), |
| context_->GetDocument())); |
| } |
| |
| namespace { |
| |
| // Returns a :where(:scope) selector. |
| // |
| // Nested declaration rules within @scope behave as :where(:scope) rules. |
| // |
| // https://github.com/w3c/csswg-drafts/issues/10431 |
| HeapVector<CSSSelector> WhereScopeSelector() { |
| HeapVector<CSSSelector> selectors; |
| |
| // An internal :true pseuo-class with a kScopeActivation relation type |
| // must precede any compound which contains :scope. |
| // See CSSSelector::RelationType::kScopeActivation. |
| CSSSelector true_selector; |
| true_selector.SetTrue(); |
| true_selector.SetRelation(CSSSelector::kScopeActivation); |
| selectors.push_back(true_selector); |
| |
| CSSSelector inner[1] = { |
| CSSSelector(AtomicString("scope"), /* implicit */ false)}; |
| inner[0].SetLastInComplexSelector(true); |
| inner[0].SetLastInSelectorList(true); |
| CSSSelectorList* inner_list = |
| CSSSelectorList::AdoptSelectorVector(base::span<CSSSelector>(inner)); |
| |
| CSSSelector where; |
| where.SetWhere(inner_list); |
| selectors.push_back(where); |
| |
| selectors.back().SetLastInComplexSelector(true); |
| selectors.back().SetLastInSelectorList(true); |
| |
| return selectors; |
| } |
| |
| } // namespace |
| |
| StyleRuleNestedDeclarations* CSSParserImpl::CreateNestedDeclarationsRule( |
| CSSNestingType nesting_type, |
| const CSSSelector* selector_list, |
| wtf_size_t start_index, |
| wtf_size_t end_index) { |
| DCHECK(RuntimeEnabledFeatures::CSSNestedDeclarationsEnabled()); |
| DCHECK(selector_list); |
| DCHECK_LE(start_index, end_index); |
| |
| // Create a nested declarations rule containing all declarations |
| // in [start_index, end_index). |
| HeapVector<CSSPropertyValue, 64> declarations; |
| declarations.AppendRange(parsed_properties_.begin() + start_index, |
| parsed_properties_.begin() + end_index); |
| |
| // Create the selector for StyleRuleNestedDeclarations's inner StyleRule. |
| |
| HeapVector<CSSSelector> selectors; |
| |
| switch (nesting_type) { |
| case CSSNestingType::kNone: |
| NOTREACHED(); |
| case CSSNestingType::kNesting: |
| // For regular nesting, the nested declarations rule should match |
| // exactly what the parent rule matches, with top-level specificity |
| // behavior. This means the selector list is copied rather than just |
| // being referenced with '&'. |
| selectors = CSSSelectorList::Copy(selector_list); |
| break; |
| case CSSNestingType::kScope: |
| // For direct nesting within @scope |
| // (e.g. .foo { @scope (...) { color:green } }), |
| // the nested declarations rule should match like a :where(:scope) rule. |
| // |
| // https://github.com/w3c/csswg-drafts/issues/10431 |
| selectors = WhereScopeSelector(); |
| break; |
| } |
| |
| return MakeGarbageCollected<StyleRuleNestedDeclarations>(StyleRule::Create( |
| base::span<CSSSelector>{selectors.begin(), selectors.size()}, |
| CreateCSSPropertyValueSet(declarations, context_->Mode(), |
| context_->GetDocument()))); |
| } |
| |
| void CSSParserImpl::EmitNestedDeclarationsRuleIfNeeded( |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| wtf_size_t start_index, |
| HeapVector<Member<StyleRuleBase>, 4>& child_rules) { |
| if (!RuntimeEnabledFeatures::CSSNestedDeclarationsEnabled()) { |
| return; |
| } |
| if (!parent_rule_for_nesting) { |
| // This can happen for @page, which behaves similarly to CSS Nesting |
| // (and cares about child rules), but doesn't have a parent style rule. |
| return; |
| } |
| wtf_size_t end_index = parsed_properties_.size(); |
| if (start_index == kNotFound) { |
| return; |
| } |
| // The spec only allows creating non-empty rules, however, the inspector needs |
| // empty rules to appear as well. This has no effect on the styles seen by |
| // the page (the styles parsed with an `observer_` are for local use in the |
| // inspector only). |
| const bool emit_empty_rule = observer_; |
| if (start_index >= end_index && !emit_empty_rule) { |
| return; |
| } |
| |
| StyleRuleNestedDeclarations* nested_declarations_rule = |
| CreateNestedDeclarationsRule(nesting_type, |
| parent_rule_for_nesting->FirstSelector(), |
| start_index, end_index); |
| DCHECK(nested_declarations_rule); |
| child_rules.push_back(nested_declarations_rule); |
| |
| if (observer_) { |
| observer_->ObserveNestedDeclarations( |
| /* insert_rule_index */ child_rules.size() - 1); |
| } |
| |
| // The declarations held by the nested declarations rule |
| // should not *also* appear in the main style declarations of the parent rule. |
| parsed_properties_.resize(start_index); |
| } |
| |
| StyleRuleMedia* CSSParserImpl::ConsumeMediaRule( |
| CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| // Consume the prelude. |
| |
| // First just get the string for the prelude to see if we've got a cached |
| // version of this. (This is mainly to save memory in certain page with |
| // lots of duplicate media queries.) |
| CSSParserTokenStream::State savepoint = stream.Save(); |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| stream.SkipUntilPeekedTypeIs<kLeftBraceToken, kSemicolonToken>(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| |
| String prelude_string = |
| stream |
| .StringRangeAt(prelude_offset_start, |
| prelude_offset_end - prelude_offset_start) |
| .ToString(); |
| const MediaQuerySet* media; |
| Member<const MediaQuerySet>& cached_media = |
| media_query_cache_.insert(prelude_string, nullptr).stored_value->value; |
| if (cached_media) { |
| media = cached_media.Get(); |
| } else { |
| // Not in the cache, so we'll have to rewind and actually parse it. |
| // Note that the media query set grammar doesn't really have an idea |
| // of when the stream should end; if it sees something it doesn't |
| // understand (which includes a left brace), it will just forward to |
| // the next comma, skipping over the entire stylesheet until the end. |
| // The grammar is generally written in the understanding that the prelude |
| // is extracted as a string and only then parsed, whereas we do fully |
| // streaming prelude parsing. Thus, we need to set some boundaries |
| // here ourselves to make sure we end when the prelude does; the alternative |
| // would be to teach the media query set parser to stop there itself. |
| stream.Restore(savepoint); |
| CSSParserTokenStream::Boundary boundary(stream, kLeftBraceToken); |
| CSSParserTokenStream::Boundary boundary2(stream, kSemicolonToken); |
| media = MediaQueryParser::ParseMediaQuerySet( |
| stream, context_->GetExecutionContext()); |
| } |
| DCHECK(media); |
| |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleMedia)) { |
| return nullptr; |
| } |
| |
| cached_media = media; |
| |
| // Consume the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kMedia, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| if (style_sheet_) { |
| style_sheet_->SetHasMediaQueries(); |
| } |
| |
| HeapVector<Member<StyleRuleBase>, 4> rules; |
| ConsumeRuleListOrNestedDeclarationList( |
| stream, |
| /* is_nested_group_rule */ nesting_type == CSSNestingType::kNesting, |
| nesting_type, parent_rule_for_nesting, &rules); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| // NOTE: There will be a copy of rules here, to deal with the different inline |
| // size. |
| return MakeGarbageCollected<StyleRuleMedia>(media, std::move(rules)); |
| } |
| |
| StyleRuleSupports* CSSParserImpl::ConsumeSupportsRule( |
| CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| CSSSupportsParser::Result supported = |
| CSSSupportsParser::ConsumeSupportsCondition(stream, *this); |
| if (supported == CSSSupportsParser::Result::kParseFailure) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleSupports); |
| return nullptr; |
| } |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleSupports)) { |
| return nullptr; |
| } |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| 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>, 4> rules; |
| ConsumeRuleListOrNestedDeclarationList( |
| stream, |
| /* is_nested_group_rule */ nesting_type == CSSNestingType::kNesting, |
| nesting_type, parent_rule_for_nesting, &rules); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| // NOTE: There will be a copy of rules here, to deal with the different inline |
| // size. |
| return MakeGarbageCollected<StyleRuleSupports>( |
| prelude_serialized, supported == CSSSupportsParser::Result::kSupported, |
| std::move(rules)); |
| } |
| |
| StyleRuleStartingStyle* CSSParserImpl::ConsumeStartingStyleRule( |
| CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| // NOTE: @starting-style prelude should be empty. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRuleStartingStyle)) { |
| return nullptr; |
| } |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kStartingStyle, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| HeapVector<Member<StyleRuleBase>, 4> rules; |
| ConsumeRuleListOrNestedDeclarationList( |
| stream, |
| /* is_nested_group_rule */ nesting_type == CSSNestingType::kNesting, |
| nesting_type, parent_rule_for_nesting, &rules); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| // NOTE: There will be a copy of rules here, to deal with the different inline |
| // size. |
| return MakeGarbageCollected<StyleRuleStartingStyle>(std::move(rules)); |
| } |
| |
| StyleRuleFontFace* CSSParserImpl::ConsumeFontFaceRule( |
| CSSParserTokenStream& stream) { |
| // Consume the prelude. |
| // NOTE: @font-face prelude should be empty. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleFontFace)) { |
| return nullptr; |
| } |
| |
| // Consume the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kFontFace, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| // TODO(sesse): Is this really right? |
| observer_->StartRuleBody(prelude_offset_end); |
| observer_->EndRuleBody(prelude_offset_end); |
| } |
| |
| if (style_sheet_) { |
| style_sheet_->SetHasFontFaceRule(); |
| } |
| |
| base::AutoReset<CSSParserObserver*> disable_observer(&observer_, nullptr); |
| ConsumeDeclarationList(stream, StyleRule::kFontFace, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| return MakeGarbageCollected<StyleRuleFontFace>(CreateCSSPropertyValueSet( |
| parsed_properties_, kCSSFontFaceRuleMode, context_->GetDocument())); |
| } |
| |
| StyleRuleKeyframes* CSSParserImpl::ConsumeKeyframesRule( |
| bool webkit_prefixed, |
| CSSParserTokenStream& stream) { |
| // Parse the prelude, expecting a single non-whitespace token. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| const CSSParserToken& name_token = stream.Peek(); |
| 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 { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleKeyframes); |
| return nullptr; // Parse error; expected ident token in @keyframes header |
| } |
| stream.ConsumeIncludingWhitespace(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRuleKeyframes)) { |
| return nullptr; |
| } |
| |
| // Parse the body. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| 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, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| [keyframe_rule](StyleRuleBase* keyframe, wtf_size_t) { |
| keyframe_rule->ParserAppendKeyframe(To<StyleRuleKeyframe>(keyframe)); |
| }); |
| keyframe_rule->SetName(name); |
| keyframe_rule->SetVendorPrefixed(webkit_prefixed); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| return keyframe_rule; |
| } |
| |
| StyleRuleFontFeature* CSSParserImpl::ConsumeFontFeatureRule( |
| CSSAtRuleID rule_id, |
| CSSParserTokenStream& stream) { |
| std::optional<StyleRuleFontFeature::FeatureType> feature_type = |
| ToStyleRuleFontFeatureType(rule_id); |
| if (!feature_type) { |
| return nullptr; |
| } |
| |
| wtf_size_t max_allowed_values = 1; |
| if (feature_type == StyleRuleFontFeature::FeatureType::kCharacterVariant) { |
| max_allowed_values = 2; |
| } |
| if (feature_type == StyleRuleFontFeature::FeatureType::kStyleset) { |
| max_allowed_values = std::numeric_limits<wtf_size_t>::max(); |
| } |
| |
| stream.ConsumeWhitespace(); |
| |
| if (stream.Peek().GetType() != kLeftBraceToken) { |
| return nullptr; |
| } |
| |
| CSSParserTokenStream::BlockGuard guard(stream); |
| stream.ConsumeWhitespace(); |
| |
| auto* font_feature_rule = |
| MakeGarbageCollected<StyleRuleFontFeature>(*feature_type); |
| |
| while (!stream.AtEnd()) { |
| const CSSParserToken& alias_token = stream.Peek(); |
| |
| if (alias_token.GetType() != kIdentToken) { |
| return nullptr; |
| } |
| AtomicString alias = |
| stream.ConsumeIncludingWhitespace().Value().ToAtomicString(); |
| |
| const CSSParserToken& colon_token = stream.Peek(); |
| |
| if (colon_token.GetType() != kColonToken) { |
| return nullptr; |
| } |
| |
| stream.UncheckedConsume(); |
| stream.ConsumeWhitespace(); |
| |
| CSSValueList* numbers = CSSValueList::CreateSpaceSeparated(); |
| |
| stream.ConsumeWhitespace(); |
| |
| do { |
| if (numbers->length() == max_allowed_values) { |
| return nullptr; |
| } |
| CSSPrimitiveValue* parsed_number = |
| css_parsing_utils::ConsumeIntegerOrNumberCalc( |
| stream, *context_, |
| CSSPrimitiveValue::ValueRange::kNonNegativeInteger); |
| if (!parsed_number) { |
| return nullptr; |
| } |
| numbers->Append(*parsed_number); |
| } while (stream.Peek().GetType() != kSemicolonToken && !stream.AtEnd()); |
| |
| if (!stream.AtEnd()) { |
| stream.ConsumeIncludingWhitespace(); // kSemicolonToken |
| } |
| |
| if (!numbers->length()) { |
| return nullptr; |
| } |
| |
| Vector<uint32_t> parsed_numbers; |
| for (auto value : *numbers) { |
| const CSSPrimitiveValue* number_value = |
| DynamicTo<CSSPrimitiveValue>(*value); |
| if (!number_value) { |
| return nullptr; |
| } |
| parsed_numbers.push_back(number_value->GetIntValue()); |
| } |
| |
| const CSSParserToken& expected_semicolon = stream.Peek(); |
| if (expected_semicolon.GetType() == kSemicolonToken) { |
| stream.UncheckedConsume(); |
| } |
| stream.ConsumeWhitespace(); |
| |
| font_feature_rule->UpdateAlias(alias, std::move(parsed_numbers)); |
| } |
| |
| return font_feature_rule; |
| } |
| |
| StyleRuleFontFeatureValues* CSSParserImpl::ConsumeFontFeatureValuesRule( |
| CSSParserTokenStream& stream) { |
| // Parse the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| CSSValueList* family_list = css_parsing_utils::ConsumeFontFamily(stream); |
| if (!family_list || !family_list->length()) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFontFeatureValues); |
| return nullptr; |
| } |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRuleFontFeatureValues)) { |
| return nullptr; |
| } |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kFontFeatureValues, |
| prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| // Parse the actual block. |
| |
| // The nesting logic for parsing @font-feature-values looks as follow: |
| // 1) ConsumeRuleList, calls ConsumeAtRule, and in turn ConsumeAtRuleContents |
| // 2) ConsumeAtRuleContents uses new ids for inner at-rules, for swash, |
| // styleset etc. |
| // 3) ConsumeFeatureRule (with type) consumes the inner mappings from aliases |
| // to number lists. |
| |
| FontFeatureAliases stylistic; |
| FontFeatureAliases styleset; |
| FontFeatureAliases character_variant; |
| FontFeatureAliases swash; |
| FontFeatureAliases ornaments; |
| FontFeatureAliases annotation; |
| |
| HeapVector<Member<StyleRuleFontFeature>> feature_rules; |
| bool had_valid_rules = false; |
| // ConsumeRuleList returns true only if the first rule is true, but we need to |
| // be more generous with the internals of what's inside a font feature value |
| // declaration, e.g. inside a @stylsitic, @styleset, etc. |
| if (ConsumeRuleList( |
| stream, kFontFeatureRuleList, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| [&feature_rules, &had_valid_rules](StyleRuleBase* rule, wtf_size_t) { |
| if (rule) { |
| had_valid_rules = true; |
| } |
| feature_rules.push_back(To<StyleRuleFontFeature>(rule)); |
| }) || |
| had_valid_rules) { |
| // https://drafts.csswg.org/css-fonts-4/#font-feature-values-syntax |
| // "Specifying the same <font-feature-value-type> more than once is valid; |
| // their contents are cascaded together." |
| for (auto& feature_rule : feature_rules) { |
| switch (feature_rule->GetFeatureType()) { |
| case StyleRuleFontFeature::FeatureType::kStylistic: |
| feature_rule->OverrideAliasesIn(stylistic); |
| break; |
| case StyleRuleFontFeature::FeatureType::kStyleset: |
| feature_rule->OverrideAliasesIn(styleset); |
| break; |
| case StyleRuleFontFeature::FeatureType::kCharacterVariant: |
| feature_rule->OverrideAliasesIn(character_variant); |
| break; |
| case StyleRuleFontFeature::FeatureType::kSwash: |
| feature_rule->OverrideAliasesIn(swash); |
| break; |
| case StyleRuleFontFeature::FeatureType::kOrnaments: |
| feature_rule->OverrideAliasesIn(ornaments); |
| break; |
| case StyleRuleFontFeature::FeatureType::kAnnotation: |
| feature_rule->OverrideAliasesIn(annotation); |
| break; |
| } |
| } |
| } |
| |
| Vector<AtomicString> families; |
| for (const auto family_entry : *family_list) { |
| const CSSFontFamilyValue* family_value = |
| DynamicTo<CSSFontFamilyValue>(*family_entry); |
| if (!family_value) { |
| return nullptr; |
| } |
| families.push_back(family_value->Value()); |
| } |
| |
| auto* feature_values_rule = MakeGarbageCollected<StyleRuleFontFeatureValues>( |
| std::move(families), stylistic, styleset, character_variant, swash, |
| ornaments, annotation); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| return feature_values_rule; |
| } |
| |
| // Parse an @page rule, with contents. |
| StyleRulePage* CSSParserImpl::ConsumePageRule(CSSParserTokenStream& stream) { |
| // Parse the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| CSSSelectorList* selector_list = |
| ParsePageSelector(stream, style_sheet_, *context_); |
| if (!selector_list || !selector_list->IsValid()) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRulePage); |
| return nullptr; // Parse error, invalid @page selector |
| } |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRulePage)) { |
| return nullptr; |
| } |
| |
| // Parse the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kPage, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| HeapVector<Member<StyleRuleBase>, 4> child_rules; |
| ConsumeDeclarationList(stream, StyleRule::kPage, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| &child_rules); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| return MakeGarbageCollected<StyleRulePage>( |
| selector_list, |
| CreateCSSPropertyValueSet(parsed_properties_, context_->Mode(), |
| context_->GetDocument()), |
| child_rules); |
| } |
| |
| StyleRuleProperty* CSSParserImpl::ConsumePropertyRule( |
| CSSParserTokenStream& stream) { |
| // Parse the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| const CSSParserToken& name_token = stream.ConsumeIncludingWhitespace(); |
| if (!CSSVariableParser::IsValidVariableName(name_token)) { |
| if (observer_) { |
| observer_->ObserveErroneousAtRule(prelude_offset_start, |
| CSSAtRuleID::kCSSAtRuleProperty); |
| } |
| return nullptr; |
| } |
| String name = name_token.Value().ToString(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleProperty)) { |
| return nullptr; |
| } |
| |
| // Parse the body. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kProperty, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| ConsumeDeclarationList(stream, StyleRule::kProperty, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| StyleRuleProperty* rule = MakeGarbageCollected<StyleRuleProperty>( |
| name, CreateCSSPropertyValueSet(parsed_properties_, kCSSPropertyRuleMode, |
| context_->GetDocument())); |
| |
| std::optional<CSSSyntaxDefinition> syntax = |
| PropertyRegistration::ConvertSyntax(rule->GetSyntax()); |
| std::optional<bool> inherits = |
| PropertyRegistration::ConvertInherits(rule->Inherits()); |
| std::optional<const CSSValue*> initial = |
| syntax.has_value() ? PropertyRegistration::ConvertInitial( |
| rule->GetInitialValue(), *syntax, *context_) |
| : std::nullopt; |
| |
| bool invalid_rule = |
| !syntax.has_value() || !inherits.has_value() || !initial.has_value(); |
| |
| if (observer_ && invalid_rule) { |
| Vector<CSSPropertyID, 2> failed_properties; |
| if (!syntax.has_value()) { |
| failed_properties.push_back(CSSPropertyID::kSyntax); |
| } |
| if (!inherits.has_value()) { |
| failed_properties.push_back(CSSPropertyID::kInherits); |
| } |
| if (!initial.has_value() && syntax.has_value()) { |
| failed_properties.push_back(CSSPropertyID::kInitialValue); |
| } |
| DCHECK(!failed_properties.empty()); |
| observer_->ObserveErroneousAtRule(prelude_offset_start, |
| CSSAtRuleID::kCSSAtRuleProperty, |
| failed_properties); |
| } |
| if (invalid_rule) { |
| return nullptr; |
| } |
| return rule; |
| } |
| |
| StyleRuleCounterStyle* CSSParserImpl::ConsumeCounterStyleRule( |
| CSSParserTokenStream& stream) { |
| // Parse the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| AtomicString name = css_parsing_utils::ConsumeCounterStyleNameInPrelude( |
| stream, *GetContext()); |
| if (!name) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleCounterStyle); |
| return nullptr; |
| } |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRuleCounterStyle)) { |
| return nullptr; |
| } |
| |
| // Parse the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kCounterStyle, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| ConsumeDeclarationList(stream, StyleRule::kCounterStyle, |
| CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| return MakeGarbageCollected<StyleRuleCounterStyle>( |
| name, CreateCSSPropertyValueSet(parsed_properties_, context_->Mode(), |
| context_->GetDocument())); |
| } |
| |
| StyleRuleFontPaletteValues* CSSParserImpl::ConsumeFontPaletteValuesRule( |
| CSSParserTokenStream& stream) { |
| // Parse the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| const CSSParserToken& name_token = stream.Peek(); |
| if (!css_parsing_utils::IsDashedIdent(name_token)) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFontPaletteValues); |
| return nullptr; |
| } |
| AtomicString name = name_token.Value().ToAtomicString(); |
| if (!name) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFontPaletteValues); |
| return nullptr; |
| } |
| stream.ConsumeIncludingWhitespace(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRuleFontPaletteValues)) { |
| return nullptr; |
| } |
| |
| // Parse the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kFontPaletteValues, |
| prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| ConsumeDeclarationList(stream, StyleRule::kFontPaletteValues, |
| CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| return MakeGarbageCollected<StyleRuleFontPaletteValues>( |
| name, CreateCSSPropertyValueSet(parsed_properties_, |
| kCSSFontPaletteValuesRuleMode, |
| context_->GetDocument())); |
| } |
| |
| StyleRuleBase* CSSParserImpl::ConsumeScopeRule( |
| CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| // Parse the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| auto* style_scope = |
| StyleScope::Parse(stream, context_, nesting_type, parent_rule_for_nesting, |
| is_within_scope_, style_sheet_); |
| if (!style_scope) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleScope); |
| return nullptr; |
| } |
| |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleScope)) { |
| return nullptr; |
| } |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kScope, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| // Parse the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| base::AutoReset<bool> auto_is_within_scope(&is_within_scope_, true); |
| |
| HeapVector<Member<StyleRuleBase>, 4> rules; |
| ConsumeRuleListOrNestedDeclarationList( |
| stream, |
| /* is_nested_group_rule */ nesting_type == CSSNestingType::kNesting, |
| CSSNestingType::kScope, style_scope->RuleForNesting(), &rules); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| return MakeGarbageCollected<StyleRuleScope>(*style_scope, std::move(rules)); |
| } |
| |
| StyleRuleViewTransition* CSSParserImpl::ConsumeViewTransitionRule( |
| CSSParserTokenStream& stream) { |
| CHECK(RuntimeEnabledFeatures::ViewTransitionOnNavigationEnabled()); |
| // NOTE: @view-transition prelude should be empty. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRuleViewTransition)) { |
| return nullptr; |
| } |
| |
| CSSParserTokenStream::BlockGuard guard(stream); |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kViewTransition, |
| prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| ConsumeDeclarationList(stream, StyleRule::kViewTransition, |
| CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| return MakeGarbageCollected<StyleRuleViewTransition>( |
| *CreateCSSPropertyValueSet(parsed_properties_, context_->Mode(), |
| context_->GetDocument())); |
| } |
| |
| StyleRuleContainer* CSSParserImpl::ConsumeContainerRule( |
| CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| // Consume the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| ContainerQueryParser query_parser(*context_); |
| |
| // <container-name> |
| AtomicString name; |
| if (stream.Peek().GetType() == kIdentToken) { |
| auto* ident = DynamicTo<CSSCustomIdentValue>( |
| css_parsing_utils::ConsumeSingleContainerName(stream, *context_)); |
| if (ident) { |
| name = ident->Value(); |
| } |
| } |
| |
| const MediaQueryExpNode* query = query_parser.ParseCondition(stream); |
| if (!query) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleContainer); |
| return nullptr; |
| } |
| ContainerQuery* container_query = MakeGarbageCollected<ContainerQuery>( |
| ContainerSelector(std::move(name), *query), query); |
| |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRuleContainer)) { |
| return nullptr; |
| } |
| |
| // Consume the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kContainer, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| HeapVector<Member<StyleRuleBase>, 4> rules; |
| ConsumeRuleListOrNestedDeclarationList( |
| stream, |
| /* is_nested_group_rule */ nesting_type == CSSNestingType::kNesting, |
| nesting_type, parent_rule_for_nesting, &rules); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| // NOTE: There will be a copy of rules here, to deal with the different inline |
| // size. |
| return MakeGarbageCollected<StyleRuleContainer>(*container_query, |
| std::move(rules)); |
| } |
| |
| StyleRuleBase* CSSParserImpl::ConsumeLayerRule( |
| CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting) { |
| // Consume the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| |
| Vector<StyleRuleBase::LayerName> names; |
| while (!stream.AtEnd() && stream.Peek().GetType() != kLeftBraceToken && |
| stream.Peek().GetType() != kSemicolonToken) { |
| if (names.size()) { |
| if (!css_parsing_utils::ConsumeCommaIncludingWhitespace(stream)) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleLayer); |
| return nullptr; |
| } |
| } |
| StyleRuleBase::LayerName name = ConsumeCascadeLayerName(stream); |
| if (!name.size()) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleLayer); |
| return nullptr; |
| } |
| names.push_back(std::move(name)); |
| } |
| |
| // @layer statement rule without style declarations. |
| if (stream.AtEnd() || stream.UncheckedPeek().GetType() == kSemicolonToken) { |
| if (!names.size()) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleLayer); |
| return nullptr; |
| } |
| |
| if (nesting_type == CSSNestingType::kNesting) { |
| // @layer statement rules are not group rules, and can therefore |
| // not be nested. |
| // |
| // https://drafts.csswg.org/css-nesting-1/#nested-group-rules |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleLayer); |
| return nullptr; |
| } |
| |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithoutBlock( |
| stream, CSSAtRuleID::kCSSAtRuleLayer)) { |
| return nullptr; |
| } |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kLayerStatement, |
| prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(prelude_offset_end); |
| observer_->EndRuleBody(prelude_offset_end); |
| } |
| |
| return MakeGarbageCollected<StyleRuleLayerStatement>(std::move(names)); |
| } |
| |
| // @layer block rule with style declarations. |
| StyleRuleBase::LayerName name; |
| if (names.empty()) { |
| name.push_back(g_empty_atom); |
| } else if (names.size() > 1) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleLayer); |
| return nullptr; |
| } else { |
| name = std::move(names[0]); |
| } |
| |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleLayer)) { |
| return nullptr; |
| } |
| |
| // Consume the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kLayerBlock, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| HeapVector<Member<StyleRuleBase>, 4> rules; |
| ConsumeRuleListOrNestedDeclarationList( |
| stream, |
| /* is_nested_group_rule */ nesting_type == CSSNestingType::kNesting, |
| nesting_type, parent_rule_for_nesting, &rules); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.Offset()); |
| } |
| |
| return MakeGarbageCollected<StyleRuleLayerBlock>(std::move(name), |
| std::move(rules)); |
| } |
| |
| StyleRulePositionTry* CSSParserImpl::ConsumePositionTryRule( |
| CSSParserTokenStream& stream) { |
| // Parse the prelude. |
| wtf_size_t prelude_offset_start = stream.LookAheadOffset(); |
| const CSSParserToken& name_token = stream.Peek(); |
| // <dashed-ident>, and -internal-* for UA sheets only. |
| String name; |
| if (name_token.GetType() == kIdentToken) { |
| name = name_token.Value().ToString(); |
| if (!name.StartsWith("--") && |
| !(context_->Mode() == kUASheetMode && name.StartsWith("-internal-"))) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRulePositionTry); |
| return nullptr; |
| } |
| } else { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRulePositionTry); |
| return nullptr; |
| } |
| stream.ConsumeIncludingWhitespace(); |
| wtf_size_t prelude_offset_end = stream.LookAheadOffset(); |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock( |
| stream, CSSAtRuleID::kCSSAtRulePositionTry)) { |
| return nullptr; |
| } |
| |
| // Parse the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kPositionTry, prelude_offset_start); |
| observer_->EndRuleHeader(prelude_offset_end); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| |
| ConsumeDeclarationList(stream, StyleRule::kPositionTry, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| |
| return MakeGarbageCollected<StyleRulePositionTry>( |
| AtomicString(name), |
| CreateCSSPropertyValueSet(parsed_properties_, kCSSPositionTryRuleMode, |
| context_->GetDocument())); |
| } |
| |
| // Parse a type for CSS Functions; e.g. length, color, etc.. |
| // These are being converted to the syntax used by registered custom properties. |
| // The parameter is assumed to be a single ident token. |
| static std::optional<StyleRuleFunction::Type> ParseFunctionType( |
| StringView type_name) { |
| std::optional<CSSSyntaxDefinition> syntax_def; |
| if (type_name == "any") { |
| syntax_def = CSSSyntaxStringParser("*").Parse(); |
| } else { |
| syntax_def = |
| CSSSyntaxStringParser("<" + type_name.ToString() + ">").Parse(); |
| } |
| if (!syntax_def) { |
| return {}; |
| } |
| |
| CHECK_EQ(syntax_def->Components().size(), 1u); |
| bool should_add_implicit_calc = false; |
| if (!syntax_def->IsUniversal()) { |
| // These are all the supported values in CSSSyntaxDefinition that are |
| // acceptable as inputs to calc(); see |
| // https://drafts.csswg.org/css-values/#math. |
| switch (syntax_def->Components()[0].GetType()) { |
| case CSSSyntaxType::kLength: |
| // kFrequency is missing. |
| case CSSSyntaxType::kAngle: |
| case CSSSyntaxType::kTime: |
| // kFlex is missing. |
| case CSSSyntaxType::kResolution: |
| case CSSSyntaxType::kPercentage: |
| case CSSSyntaxType::kNumber: |
| case CSSSyntaxType::kInteger: |
| case CSSSyntaxType::kLengthPercentage: |
| should_add_implicit_calc = true; |
| break; |
| case CSSSyntaxType::kTokenStream: |
| case CSSSyntaxType::kIdent: |
| case CSSSyntaxType::kColor: |
| case CSSSyntaxType::kImage: |
| case CSSSyntaxType::kUrl: |
| case CSSSyntaxType::kTransformFunction: |
| case CSSSyntaxType::kTransformList: |
| case CSSSyntaxType::kCustomIdent: |
| break; |
| case CSSSyntaxType::kString: |
| DCHECK(RuntimeEnabledFeatures::CSSAtPropertyStringSyntaxEnabled()); |
| break; |
| } |
| } |
| |
| return StyleRuleFunction::Type{std::move(*syntax_def), |
| should_add_implicit_calc}; |
| } |
| |
| StyleRuleFunction* CSSParserImpl::ConsumeFunctionRule( |
| CSSParserTokenStream& stream) { |
| // Parse the prelude; first a function token (the name), then parameters, |
| // then return type. |
| if (stream.Peek().GetType() != kFunctionToken) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFunction); |
| return nullptr; // Parse error. |
| } |
| AtomicString name = |
| stream.Peek() |
| .Value() |
| .ToAtomicString(); // Includes the opening parenthesis. |
| std::optional<Vector<StyleRuleFunction::Parameter>> parameters; |
| { |
| CSSParserTokenStream::BlockGuard guard(stream); |
| stream.ConsumeWhitespace(); |
| parameters = ConsumeFunctionParameters(stream); |
| } |
| if (!parameters.has_value()) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFunction); |
| return nullptr; |
| } |
| stream.ConsumeWhitespace(); |
| |
| // Parse the return type. |
| if (stream.Peek().GetType() != kColonToken) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFunction); |
| return nullptr; |
| } |
| stream.ConsumeIncludingWhitespace(); |
| |
| if (stream.Peek().GetType() != kIdentToken) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFunction); |
| return nullptr; |
| } |
| StringView return_type_name = stream.Peek().Value(); |
| std::optional<StyleRuleFunction::Type> return_type = |
| ParseFunctionType(return_type_name); |
| if (!return_type) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleFunction); |
| return nullptr; // Invalid type name. |
| } |
| stream.ConsumeIncludingWhitespace(); |
| |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleFunction)) { |
| return nullptr; |
| } |
| |
| // Parse the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| stream.ConsumeWhitespace(); |
| |
| // TODO: Parse local variables. |
| |
| // Parse @return. |
| if (stream.Peek().GetType() != kAtKeywordToken) { |
| return nullptr; |
| } |
| const CSSParserToken return_token = stream.ConsumeIncludingWhitespace(); |
| if (return_token.Value() != "return") { |
| return nullptr; |
| } |
| |
| // Parse the actual returned value. |
| CSSVariableData* return_value = nullptr; |
| { |
| CSSParserTokenStream::Boundary boundary(stream, kSemicolonToken); |
| bool important_ignored; |
| return_value = CSSVariableParser::ConsumeUnparsedDeclaration( |
| stream, /*allow_important_annotation=*/false, |
| /*is_animation_tainted=*/false, |
| /*must_contain_variable_reference=*/false, /*restricted_value=*/false, |
| /*comma_ends_declaration=*/false, important_ignored, *context_); |
| } |
| |
| while (!stream.AtEnd()) { |
| const CSSParserToken token = stream.ConsumeIncludingWhitespace(); |
| StringBuilder sb; |
| token.Serialize(sb); |
| } |
| |
| return MakeGarbageCollected<StyleRuleFunction>( |
| name, std::move(*parameters), return_value, std::move(*return_type)); |
| } |
| |
| StyleRuleMixin* CSSParserImpl::ConsumeMixinRule(CSSParserTokenStream& stream) { |
| // @mixin must be top-level, and as such, we need to clear the arena |
| // after we're done parsing it (like ConsumeStyleRule() does). |
| if (in_nested_style_rule_) { |
| return nullptr; |
| } |
| auto func_clear_arena = [&](HeapVector<CSSSelector>* arena) { |
| arena->resize(0); // See class comment on CSSSelectorParser. |
| }; |
| std::unique_ptr<HeapVector<CSSSelector>, decltype(func_clear_arena)> |
| scope_guard(&arena_, std::move(func_clear_arena)); |
| |
| // Parse the prelude; just a function token (the name). |
| if (stream.Peek().GetType() != kIdentToken) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleMixin); |
| return nullptr; // Parse error. |
| } |
| AtomicString name = |
| stream.ConsumeIncludingWhitespace().Value().ToAtomicString(); |
| if (!name.StartsWith("--")) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleMixin); |
| return nullptr; |
| } |
| |
| if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream, |
| CSSAtRuleID::kCSSAtRuleMixin)) { |
| return nullptr; |
| } |
| |
| // Parse the actual block. |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| // The destructor expects there to be at least one selector in the StyleRule. |
| CSSSelector dummy; |
| StyleRule* fake_parent_rule = StyleRule::Create(base::span_from_ref(dummy)); |
| HeapVector<Member<StyleRuleBase>, 4> child_rules; |
| ConsumeRuleListOrNestedDeclarationList(stream, |
| /*is_nested_group_rule=*/true, |
| CSSNestingType::kNesting, |
| fake_parent_rule, &child_rules); |
| for (StyleRuleBase* child_rule : child_rules) { |
| fake_parent_rule->AddChildRule(child_rule); |
| } |
| return MakeGarbageCollected<StyleRuleMixin>(name, fake_parent_rule); |
| } |
| |
| StyleRuleApplyMixin* CSSParserImpl::ConsumeApplyMixinRule( |
| CSSParserTokenStream& stream) { |
| if (stream.Peek().GetType() != kIdentToken) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleApplyMixin); |
| return nullptr; // Parse error. |
| } |
| AtomicString name = |
| stream.ConsumeIncludingWhitespace().Value().ToAtomicString(); |
| if (!name.StartsWith("--")) { |
| ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleApplyMixin); |
| return nullptr; |
| } |
| if (!ConsumeEndOfPreludeForAtRuleWithoutBlock( |
| stream, CSSAtRuleID::kCSSAtRuleApplyMixin)) { |
| return nullptr; |
| } |
| return MakeGarbageCollected<StyleRuleApplyMixin>(name); |
| } |
| |
| // Parse the parameters of a CSS function: Zero or more comma-separated |
| // instances of [<name> <colon> <type>]. Returns the empty value |
| // on parse error. |
| std::optional<Vector<StyleRuleFunction::Parameter>> |
| CSSParserImpl::ConsumeFunctionParameters(CSSParserTokenStream& stream) { |
| Vector<StyleRuleFunction::Parameter> parameters; |
| bool first_parameter = true; |
| for (;;) { |
| stream.ConsumeWhitespace(); |
| |
| if (first_parameter && stream.Peek().GetType() == kRightParenthesisToken) { |
| // No arguments. |
| break; |
| } |
| if (stream.Peek().GetType() != kIdentToken) { |
| return {}; // Parse error. |
| } |
| String parameter_name = stream.Peek().Value().ToString(); |
| if (!CSSVariableParser::IsValidVariableName(parameter_name)) { |
| return {}; |
| } |
| stream.ConsumeIncludingWhitespace(); |
| |
| if (stream.Peek().GetType() != kColonToken) { |
| return {}; |
| } |
| stream.ConsumeIncludingWhitespace(); |
| |
| if (stream.Peek().GetType() != kIdentToken) { |
| return {}; |
| } |
| StringView type_name = stream.Peek().Value(); |
| std::optional<StyleRuleFunction::Type> type = ParseFunctionType(type_name); |
| if (!type) { |
| return {}; // Invalid type name. |
| } |
| stream.ConsumeIncludingWhitespace(); |
| parameters.push_back( |
| StyleRuleFunction::Parameter{parameter_name, std::move(*type)}); |
| if (stream.Peek().GetType() == kRightParenthesisToken) { |
| // No more arguments. |
| break; |
| } |
| if (stream.Peek().GetType() != kCommaToken) { |
| return {}; // Expected more parameters, or end of argument list. |
| } |
| stream.ConsumeIncludingWhitespace(); |
| first_parameter = false; |
| } |
| return parameters; |
| } |
| |
| StyleRuleKeyframe* CSSParserImpl::ConsumeKeyframeStyleRule( |
| std::unique_ptr<Vector<KeyframeOffset>> key_list, |
| const RangeOffset& prelude_offset, |
| CSSParserTokenStream& block) { |
| if (!key_list) { |
| return nullptr; |
| } |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kKeyframe, prelude_offset.start); |
| observer_->EndRuleHeader(prelude_offset.end); |
| observer_->StartRuleBody(block.Offset()); |
| } |
| |
| ConsumeDeclarationList(block, StyleRule::kKeyframe, CSSNestingType::kNone, |
| /*parent_rule_for_nesting=*/nullptr, |
| /*nested_declarations_start_index=*/kNotFound, |
| /*child_rules=*/nullptr); |
| |
| if (observer_) { |
| observer_->EndRuleBody(block.LookAheadOffset()); |
| } |
| |
| return MakeGarbageCollected<StyleRuleKeyframe>( |
| std::move(key_list), |
| CreateCSSPropertyValueSet(parsed_properties_, kCSSKeyframeRuleMode, |
| context_->GetDocument())); |
| } |
| |
| // A (hopefully) fast check for whether the given declaration block could |
| // contain nested CSS rules. All of these have to involve { in some shape |
| // or form, so we simply check for the existence of that. (It means we will |
| // have false positives for e.g. { within comments or strings, but this |
| // only means we will turn off lazy parsing for that rule, nothing worse.) |
| // This will work even for UTF-16, although with some more false positives |
| // with certain Unicode characters such as U+017E (LATIN SMALL LETTER Z |
| // WITH CARON). This is, again, not a big problem for us. |
| static bool MayContainNestedRules(const String& text, |
| wtf_size_t offset, |
| wtf_size_t length) { |
| if (length < 2u) { |
| // {} is the shortest possible block (but if there's |
| // a lone { and then EOF, we will be called with length 1). |
| return false; |
| } |
| |
| // Strip away the outer {} pair (the { would always give us a false positive). |
| DCHECK_EQ(text[offset], '{'); |
| if (text[offset + length - 1] != '}') { |
| // EOF within the block, so just be on the safe side |
| // and use the normal (non-lazy) code path. |
| return true; |
| } |
| ++offset; |
| length -= 2; |
| |
| size_t char_size = text.Is8Bit() ? sizeof(LChar) : sizeof(UChar); |
| auto text_bytes = base::as_chars( |
| text.RawByteSpan().subspan(offset * char_size, length * char_size)); |
| return memchr(text_bytes.data(), '{', text_bytes.size()) != nullptr; |
| } |
| |
| StyleRule* CSSParserImpl::ConsumeStyleRule(CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| bool nested, |
| bool& invalid_rule_error) { |
| if (!in_nested_style_rule_) { |
| DCHECK_EQ(0u, arena_.size()); |
| } |
| auto func_clear_arena = [&](HeapVector<CSSSelector>* arena) { |
| if (!in_nested_style_rule_) { |
| arena->resize(0); // See class comment on CSSSelectorParser. |
| } |
| }; |
| std::unique_ptr<HeapVector<CSSSelector>, decltype(func_clear_arena)> |
| scope_guard(&arena_, std::move(func_clear_arena)); |
| |
| if (observer_) { |
| observer_->StartRuleHeader(StyleRule::kStyle, stream.LookAheadOffset()); |
| } |
| |
| // Style rules that look like custom property declarations |
| // are not allowed by css-syntax. |
| // |
| // https://drafts.csswg.org/css-syntax/#consume-qualified-rule |
| bool custom_property_ambiguity = false; |
| if (CSSVariableParser::IsValidVariableName(stream.Peek())) { |
| CSSParserTokenStream::State state = stream.Save(); |
| stream.ConsumeIncludingWhitespace(); // <ident> |
| custom_property_ambiguity = stream.Peek().GetType() == kColonToken; |
| stream.Restore(state); |
| } |
| |
| // Parse the prelude of the style rule |
| base::span<CSSSelector> selector_vector = CSSSelectorParser::ConsumeSelector( |
| stream, context_, nesting_type, parent_rule_for_nesting, is_within_scope_, |
| /* semicolon_aborts_nested_selector*/ nested, style_sheet_, observer_, |
| arena_); |
| |
| if (selector_vector.empty()) { |
| // Read the rest of the prelude if there was an error |
| stream.EnsureLookAhead(); |
| if (nested) { |
| stream.SkipUntilPeekedTypeIs<kLeftBraceToken, kSemicolonToken>(); |
| } else { |
| stream.SkipUntilPeekedTypeIs<kLeftBraceToken>(); |
| } |
| } |
| |
| if (observer_) { |
| observer_->EndRuleHeader(stream.LookAheadOffset()); |
| } |
| |
| if (stream.Peek().GetType() != kLeftBraceToken) { |
| // Parse error, EOF instead of qualified rule block |
| // (or we went into error recovery above). |
| // NOTE: If we aborted due to a semicolon, don't consume it here; |
| // the caller will do that for us. |
| return nullptr; |
| } |
| |
| if (custom_property_ambiguity) { |
| if (nested) { |
| // https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration |
| // Note that the caller consumes the bad declaration remnants |
| // (see ConsumeDeclarationList). |
| return nullptr; |
| } |
| // "If nested is false, consume a block from input, and return nothing." |
| // https://drafts.csswg.org/css-syntax/#consume-qualified-rule |
| CSSParserTokenStream::BlockGuard guard(stream); |
| return nullptr; |
| } |
| // Check if rule is "valid in current context". |
| // https://drafts.csswg.org/css-syntax/#consume-qualified-rule |
| // |
| // This means checking if the selector parsed successfully. |
| if (selector_vector.empty()) { |
| CSSParserTokenStream::BlockGuard guard(stream); |
| invalid_rule_error = true; |
| return nullptr; |
| } |
| |
| if (RuntimeEnabledFeatures::CSSLazyParsingFastPathEnabled()) { |
| // TODO(csharrison): How should we lazily parse css that needs the observer? |
| if (!observer_ && lazy_state_) { |
| DCHECK(style_sheet_); |
| |
| StringView text(stream.RemainingText(), 1); |
| #ifdef ARCH_CPU_X86_FAMILY |
| wtf_size_t len; |
| if (base::CPU::GetInstanceNoAllocation().has_avx2()) { |
| len = static_cast<wtf_size_t>(FindLengthOfDeclarationListAVX2(text)); |
| } else { |
| len = static_cast<wtf_size_t>(FindLengthOfDeclarationList(text)); |
| } |
| #else |
| wtf_size_t len = |
| static_cast<wtf_size_t>(FindLengthOfDeclarationList(text)); |
| #endif |
| if (len != 0) { |
| wtf_size_t block_start_offset = stream.Offset(); |
| stream.SkipToEndOfBlock(len + 2); // +2 for { and }. |
| return StyleRule::Create( |
| selector_vector, MakeGarbageCollected<CSSLazyPropertyParserImpl>( |
| block_start_offset, lazy_state_)); |
| } |
| } |
| CSSParserTokenStream::BlockGuard guard(stream); |
| return ConsumeStyleRuleContents(selector_vector, stream); |
| } else { |
| CSSParserTokenStream::BlockGuard guard(stream); |
| |
| // TODO(csharrison): How should we lazily parse css that needs the observer? |
| if (!observer_ && lazy_state_) { |
| DCHECK(style_sheet_); |
| |
| wtf_size_t block_start_offset = stream.Offset() - 1; // - 1 for the {. |
| guard.SkipToEndOfBlock(); |
| wtf_size_t block_length = stream.Offset() - block_start_offset; |
| |
| // Lazy parsing cannot deal with nested rules. We make a very quick check |
| // to see if there could possibly be any in there; if so, we need to go |
| // back to normal (non-lazy) parsing. If that happens, we've wasted some |
| // work; specifically, the SkipToEndOfBlock(), and potentially that we |
| // cannot use the CachedCSSTokenizer if that would otherwise be in use. |
| if (MayContainNestedRules(lazy_state_->SheetText(), block_start_offset, |
| block_length)) { |
| CSSParserTokenStream block_stream(lazy_state_->SheetText(), |
| block_start_offset); |
| CSSParserTokenStream::BlockGuard sub_guard( |
| block_stream); // Consume the {, and open the block stack. |
| return ConsumeStyleRuleContents(selector_vector, block_stream); |
| } |
| |
| return StyleRule::Create(selector_vector, |
| MakeGarbageCollected<CSSLazyPropertyParserImpl>( |
| block_start_offset, lazy_state_)); |
| } |
| return ConsumeStyleRuleContents(selector_vector, stream); |
| } |
| } |
| |
| StyleRule* CSSParserImpl::ConsumeStyleRuleContents( |
| base::span<CSSSelector> selector_vector, |
| CSSParserTokenStream& stream) { |
| StyleRule* style_rule = StyleRule::Create(selector_vector); |
| HeapVector<Member<StyleRuleBase>, 4> child_rules; |
| if (observer_) { |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| ConsumeDeclarationList(stream, StyleRule::kStyle, CSSNestingType::kNesting, |
| /*parent_rule_for_nesting=*/style_rule, |
| /*nested_declarations_start_index=*/kNotFound, |
| &child_rules); |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| for (StyleRuleBase* child_rule : child_rules) { |
| style_rule->AddChildRule(child_rule); |
| } |
| style_rule->SetProperties(CreateCSSPropertyValueSet( |
| parsed_properties_, context_->Mode(), context_->GetDocument())); |
| return style_rule; |
| } |
| |
| // This function is used for two different but very similarly specified actions |
| // in [css-syntax-3], namely “parse a list of declarations” (used for style |
| // attributes, @page rules and a few other things) and “consume a style block's |
| // contents” (used for the interior of rules, such as in a normal stylesheet). |
| // The only real difference between the two is that the latter cannot contain |
| // nested rules. In particular, both have the effective behavior that when |
| // seeing something that is not an ident and is not a valid selector, we should |
| // skip to the next semicolon. (For “consume a style block's contents”, this is |
| // explicit, and for “parse a list of declarations”, it happens due to |
| // synchronization behavior. Of course, for the latter case, a _valid_ selector |
| // would get the same skipping behavior.) |
| // |
| // So as the spec stands, we can unify these cases; we use |
| // parent_rule_for_nesting as a marker for which case we are in (see [1]). |
| // If it's nullptr, we're parsing a declaration list and not a style block, |
| // so non-idents should not begin consuming qualified rules. See also |
| // AbortsNestedSelectorParsing(), which uses parent_rule_for_nesting to check |
| // whether semicolons should abort parsing (the prelude of) qualified rules; |
| // if semicolons always aborted such parsing, we wouldn't need this distinction. |
| // |
| // The `nested_declarations_start_index` parameter controls how this function |
| // emits "nested declaration" rules for the leading block of declarations. |
| // For regular style rules (which can hold declarations directly), this should |
| // be kNotFound, which will prevent a wrapper rule for the leading block. |
| // (Subsequent declarations "interleaved" with child rules will still be |
| // wrapped). For nested group rules, or generally rules that cannot hold |
| // declarations directly (e.g. @media), the parameter value should be 0u, |
| // causing the leading declarations to get wrapped as well. |
| void CSSParserImpl::ConsumeDeclarationList( |
| CSSParserTokenStream& stream, |
| StyleRule::RuleType rule_type, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| wtf_size_t nested_declarations_start_index, |
| HeapVector<Member<StyleRuleBase>, 4>* child_rules) { |
| DCHECK(parsed_properties_.empty()); |
| |
| while (true) { |
| // Having a lookahead may skip comments, which are used by the observer. |
| DCHECK(!stream.HasLookAhead() || stream.AtEnd()); |
| |
| if (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 kAtKeywordToken: { |
| CSSParserToken name_token = stream.ConsumeIncludingWhitespace(); |
| const StringView name = name_token.Value(); |
| const CSSAtRuleID id = CssAtRuleID(name); |
| bool invalid_rule_error_ignored = false; |
| StyleRuleBase* child = ConsumeNestedRule( |
| id, rule_type, stream, nesting_type, parent_rule_for_nesting, |
| invalid_rule_error_ignored); |
| // "Consume an at-rule" can't return invalid-rule-error. |
| // https://drafts.csswg.org/css-syntax/#consume-at-rule |
| DCHECK(!invalid_rule_error_ignored); |
| if (child && child_rules) { |
| EmitNestedDeclarationsRuleIfNeeded( |
| nesting_type, parent_rule_for_nesting, |
| nested_declarations_start_index, *child_rules); |
| nested_declarations_start_index = parsed_properties_.size(); |
| child_rules->push_back(child); |
| } |
| break; |
| } |
| case kIdentToken: { |
| CSSParserTokenStream::State state = stream.Save(); |
| bool consumed_declaration = false; |
| { |
| CSSParserTokenStream::Boundary boundary(stream, kSemicolonToken); |
| consumed_declaration = ConsumeDeclaration(stream, rule_type); |
| } |
| if (consumed_declaration) { |
| if (!stream.AtEnd()) { |
| DCHECK_EQ(stream.UncheckedPeek().GetType(), kSemicolonToken); |
| stream.UncheckedConsume(); // kSemicolonToken |
| } |
| break; |
| } else if (stream.Peek().GetType() == kSemicolonToken) { |
| // As an optimization, we avoid the restart below (retrying as a |
| // nested style rule) if we ended on a kSemicolonToken, as this |
| // situation can't produce a valid rule. |
| stream.UncheckedConsume(); // kSemicolonToken |
| break; |
| } |
| // Retry as nested rule. |
| stream.Restore(state); |
| [[fallthrough]]; |
| } |
| default: |
| if (parent_rule_for_nesting != nullptr) { // [1] (see function comment) |
| bool invalid_rule_error = false; |
| StyleRuleBase* child = |
| ConsumeNestedRule(std::nullopt, rule_type, stream, nesting_type, |
| parent_rule_for_nesting, invalid_rule_error); |
| if (child) { |
| if (child_rules) { |
| EmitNestedDeclarationsRuleIfNeeded( |
| nesting_type, parent_rule_for_nesting, |
| nested_declarations_start_index, *child_rules); |
| nested_declarations_start_index = parsed_properties_.size(); |
| child_rules->push_back(child); |
| } |
| break; |
| } else if (invalid_rule_error) { |
| // https://drafts.csswg.org/css-syntax/#invalid-rule-error |
| // |
| // This means the rule was valid per the "core" grammar of |
| // css-syntax, but the prelude (i.e. selector list) didn't parse. |
| // We should not fall through to error recovery in this case, |
| // because we should continue parsing immediately after |
| // the {}-block. |
| break; |
| } |
| // Fall through to error recovery. |
| stream.EnsureLookAhead(); |
| } |
| |
| [[fallthrough]]; |
| // Function tokens should start parsing a declaration |
| // (which then immediately goes into error recovery mode). |
| case CSSParserTokenType::kFunctionToken: |
| stream.SkipUntilPeekedTypeIs<kSemicolonToken>(); |
| if (!stream.UncheckedAtEnd()) { |
| stream.UncheckedConsume(); // kSemicolonToken |
| } |
| |
| break; |
| } |
| } |
| |
| // We need a final call to EmitNestedDeclarationsRuleIfNeeded in case there |
| // are trailing bare declarations. If no child rule has been observed, |
| // nested_declarations_start_index is still kNotFound (UINT_MAX), |
| // which causes EmitNestedDeclarationsRuleIfNeeded to have no effect. |
| if (child_rules) { |
| EmitNestedDeclarationsRuleIfNeeded(nesting_type, parent_rule_for_nesting, |
| nested_declarations_start_index, |
| *child_rules); |
| } |
| } |
| |
| // Consumes a list of style rules and stores the result in `child_rules`, |
| // or (if `is_nested_group_rule` is true) consumes the interior of a nested |
| // group rule [1]. Nested group rules allow a list of declarations to appear |
| // directly in place of where a list of rules would normally go. |
| // |
| // [1] https://drafts.csswg.org/css-nesting-1/#nested-group-rules |
| void CSSParserImpl::ConsumeRuleListOrNestedDeclarationList( |
| CSSParserTokenStream& stream, |
| bool is_nested_group_rule, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| HeapVector<Member<StyleRuleBase>, 4>* child_rules) { |
| DCHECK(child_rules); |
| |
| if (is_nested_group_rule) { |
| // This is a nested group rule, which allows *declarations* to appear |
| // directly within the body of the rule, e.g.: |
| // |
| // .foo { |
| // @media (width > 800px) { |
| // color: green; |
| // } |
| // } |
| // |
| // Note that nested group rules may also contain *rules* within its body. |
| // This is handled by `ConsumeDeclarationList`, see comment near that |
| // function. |
| if (RuntimeEnabledFeatures::CSSNestedDeclarationsEnabled()) { |
| // Using nested_declarations_start_index=0u here means that the leading |
| // declarations will be wrapped in a CSSNestedDeclarations rule. |
| // Unlike regular style rules, the leading declarations must be wrapped |
| // in something that can hold them, because group rules (e.g. @media) |
| // can not hold properties directly. |
| ConsumeDeclarationList( |
| stream, StyleRule::kStyle, nesting_type, parent_rule_for_nesting, |
| /* nested_declarations_start_index */ 0u, child_rules); |
| } else { |
| if (observer_) { |
| // Observe an empty rule header to ensure the observer has a new rule |
| // data on the stack for the following ConsumeDeclarationList. |
| observer_->StartRuleHeader(StyleRule::kStyle, stream.Offset()); |
| observer_->EndRuleHeader(stream.Offset()); |
| observer_->StartRuleBody(stream.Offset()); |
| } |
| ConsumeDeclarationList( |
| stream, StyleRule::kStyle, nesting_type, parent_rule_for_nesting, |
| /* nested_declarations_start_index */ kNotFound, child_rules); |
| if (observer_) { |
| observer_->EndRuleBody(stream.LookAheadOffset()); |
| } |
| if (!parsed_properties_.empty()) { |
| child_rules->push_front( |
| CreateImplicitNestedRule(nesting_type, parent_rule_for_nesting)); |
| } |
| } |
| } else { |
| ConsumeRuleList(stream, kRegularRuleList, nesting_type, |
| parent_rule_for_nesting, |
| [child_rules](StyleRuleBase* rule, wtf_size_t) { |
| child_rules->push_back(rule); |
| }); |
| } |
| } |
| |
| StyleRuleBase* CSSParserImpl::ConsumeNestedRule( |
| std::optional<CSSAtRuleID> id, |
| StyleRule::RuleType parent_rule_type, |
| CSSParserTokenStream& stream, |
| CSSNestingType nesting_type, |
| StyleRule* parent_rule_for_nesting, |
| bool& invalid_rule_error) { |
| // A nested style rule. Recurse into the parser; we need to move the parsed |
| // properties out of the way while we're parsing the child rule, though. |
| // TODO(sesse): The spec says that any properties after a nested rule |
| // should be ignored. We don't support this yet. |
| // See https://github.com/w3c/csswg-drafts/issues/7501. |
| HeapVector<CSSPropertyValue, 64> outer_parsed_properties; |
| swap(parsed_properties_, outer_parsed_properties); |
| StyleRuleBase* child; |
| base::AutoReset<bool> reset_in_nested_style_rule(&in_nested_style_rule_, |
| true); |
| if (!id.has_value()) { |
| child = ConsumeStyleRule(stream, nesting_type, parent_rule_for_nesting, |
| /* nested */ true, invalid_rule_error); |
| } else { |
| child = ConsumeAtRuleContents(*id, stream, |
| parent_rule_type == StyleRule::kPage |
| ? kPageMarginRules |
| : kNestedGroupRules, |
| nesting_type, parent_rule_for_nesting); |
| } |
| parsed_properties_ = std::move(outer_parsed_properties); |
| if (child && parent_rule_type != StyleRule::kPage) { |
| context_->Count(WebFeature::kCSSNesting); |
| } |
| return child; |
| } |
| |
| // This function can leave the stream in one of the following states: |
| // |
| // 1) If the ident token is not immediately followed by kColonToken, |
| // then the stream is left at the token where kColonToken was expected. |
| // 2) If the ident token is not a recognized property/descriptor, |
| // then the stream is left at the token immediately after kColonToken. |
| // 3) Otherwise the stream is is left AtEnd(), regardless of whether or |
| // not the value was valid. |
| // |
| // Leaving the stream in an awkward states is normally not desirable for |
| // Consume functions, but declarations are sometimes parsed speculatively, |
| // which may cause a restart at the call site (see ConsumeDeclarationList, |
| // kIdentToken branch). If we are anyway going to restart, any work we do |
| // to leave the stream in a more consistent state is just wasted. |
| bool 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) { |
| return false; // Parse error. |
| } |
| |
| stream.UncheckedConsume(); // kColonToken |
| stream.EnsureLookAhead(); |
| |
| size_t properties_count = parsed_properties_.size(); |
| |
| bool parsing_descriptor = rule_type == StyleRule::kFontFace || |
| rule_type == StyleRule::kFontPaletteValues || |
| rule_type == StyleRule::kProperty || |
| rule_type == StyleRule::kCounterStyle || |
| rule_type == StyleRule::kViewTransition; |
| |
| uint64_t id = parsing_descriptor |
| ? static_cast<uint64_t>(lhs.ParseAsAtRuleDescriptorID()) |
| : static_cast<uint64_t>(lhs.ParseAsUnresolvedCSSPropertyID( |
| context_->GetExecutionContext(), context_->Mode())); |
| |
| bool important = false; |
| |
| static_assert(static_cast<uint64_t>(AtRuleDescriptorID::Invalid) == 0u); |
| static_assert(static_cast<uint64_t>(CSSPropertyID::kInvalid) == 0u); |
| |
| stream.ConsumeWhitespace(); |
| |
| if (id) { |
| if (parsing_descriptor) { |
| const AtRuleDescriptorID atrule_id = static_cast<AtRuleDescriptorID>(id); |
| AtRuleDescriptorParser::ParseDescriptorValue( |
| rule_type, atrule_id, stream, *context_, parsed_properties_); |
| } else { |
| const CSSPropertyID unresolved_property = static_cast<CSSPropertyID>(id); |
| if (unresolved_property == CSSPropertyID::kVariable) { |
| if (rule_type != StyleRule::kStyle && |
| rule_type != StyleRule::kKeyframe) { |
| return false; |
| } |
| AtomicString variable_name = lhs.Value().ToAtomicString(); |
| bool allow_important_annotation = (rule_type != StyleRule::kKeyframe); |
| bool is_animation_tainted = rule_type == StyleRule::kKeyframe; |
| if (!ConsumeVariableValue(stream, variable_name, |
| allow_important_annotation, |
| is_animation_tainted)) { |
| return false; |
| } |
| } else if (unresolved_property != CSSPropertyID::kInvalid) { |
| if (observer_) { |
| CSSParserTokenStream::State savepoint = stream.Save(); |
| ConsumeDeclarationValue(stream, unresolved_property, |
| /*is_in_declaration_list=*/true, rule_type); |
| |
| // The observer would like to know (below) whether this declaration |
| // was !important or not. If our parse succeeded, we can just pick it |
| // out from the list of properties. If not, we'll need to look at the |
| // tokens ourselves. |
| if (parsed_properties_.size() != properties_count) { |
| important = parsed_properties_.back().IsImportant(); |
| } else { |
| stream.Restore(savepoint); |
| // NOTE: This call is solely to update “important”. |
| CSSVariableParser::ConsumeUnparsedDeclaration( |
| stream, /*allow_important_annotation=*/true, |
| /*is_animation_tainted=*/false, |
| /*must_contain_variable_reference=*/false, |
| /*restricted_value=*/true, /*comma_ends_declaration=*/false, |
| important, *context_); |
| } |
| } else { |
| ConsumeDeclarationValue(stream, unresolved_property, |
| /*is_in_declaration_list=*/true, rule_type); |
| } |
| } |
| } |
| } |
| if (observer_ && |
| (rule_type == StyleRule::kStyle || rule_type == StyleRule::kKeyframe || |
| rule_type == StyleRule::kProperty || |
| rule_type == StyleRule::kPositionTry || |
| rule_type == StyleRule::kFontPaletteValues)) { |
| if (!id) { |
| // If we skipped the relevant Consume*() calls above due to an invalid |
| // property/descriptor, the inspector still needs to know the offset |
| // where the would-be declaration ends. |
| CSSVariableParser::ConsumeUnparsedDeclaration( |
| stream, /*allow_important_annotation=*/true, |
| /*is_animation_tainted=*/false, |
| /*must_contain_variable_reference=*/false, |
| /*restricted_value=*/true, /*comma_ends_declaration=*/false, |
| important, *context_); |
| } |
| // 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); |
| } |
| |
| return parsed_properties_.size() != properties_count; |
| } |
| |
| bool CSSParserImpl::ConsumeVariableValue(CSSParserTokenStream& stream, |
| const AtomicString& variable_name, |
| bool allow_important_annotation, |
| bool is_animation_tainted) { |
| stream.EnsureLookAhead(); |
| |
| // First, see if this is (only) a CSS-wide keyword. |
| bool important; |
| const CSSValue* value = CSSPropertyParser::ConsumeCSSWideKeyword( |
| stream, allow_important_annotation, important); |
| if (!value) { |
| // It was not, so try to parse it as an unparsed declaration value |
| // (which is pretty free-form). |
| CSSVariableData* variable_data = |
| CSSVariableParser::ConsumeUnparsedDeclaration( |
| stream, allow_important_annotation, is_animation_tainted, |
| /*must_contain_variable_reference=*/false, |
| /*restricted_value=*/false, /*comma_ends_declaration=*/false, |
| important, *context_); |
| if (!variable_data) { |
| return false; |
| } |
| |
| value = MakeGarbageCollected<CSSUnparsedDeclarationValue>(variable_data, |
| context_); |
| } |
| parsed_properties_.push_back( |
| CSSPropertyValue(CSSPropertyName(variable_name), *value, important)); |
| context_->Count(context_->Mode(), CSSPropertyID::kVariable); |
| return true; |
| } |
| |
| // NOTE: Leading whitespace must be stripped from the stream, since |
| // ParseValue() has the same requirement. |
| void CSSParserImpl::ConsumeDeclarationValue(CSSParserTokenStream& stream, |
| CSSPropertyID unresolved_property, |
| bool is_in_declaration_list, |
| StyleRule::RuleType rule_type) { |
| const bool allow_important_annotation = is_in_declaration_list && |
| rule_type != StyleRule::kKeyframe && |
| rule_type != StyleRule::kPositionTry; |
| CSSPropertyParser::ParseValue(unresolved_property, allow_important_annotation, |
| stream, context_, parsed_properties_, |
| rule_type); |
| } |
| |
| std::unique_ptr<Vector<KeyframeOffset>> CSSParserImpl::ConsumeKeyframeKeyList( |
| const CSSParserContext* context, |
| CSSParserTokenStream& stream) { |
| std::unique_ptr<Vector<KeyframeOffset>> result = |
| std::make_unique<Vector<KeyframeOffset>>(); |
| while (true) { |
| stream.ConsumeWhitespace(); |
| const CSSParserToken& token = stream.Peek(); |
| if (token.GetType() == kPercentageToken && token.NumericValue() >= 0 && |
| token.NumericValue() <= 100) { |
| result->push_back(KeyframeOffset(TimelineOffset::NamedRange::kNone, |
| token.NumericValue() / 100)); |
| stream.ConsumeIncludingWhitespace(); |
| } else if (token.GetType() == kIdentToken) { |
| if (EqualIgnoringASCIICase(token.Value(), "from")) { |
| result->push_back(KeyframeOffset(TimelineOffset::NamedRange::kNone, 0)); |
| stream.ConsumeIncludingWhitespace(); |
| } else if (EqualIgnoringASCIICase(token.Value(), "to")) { |
| result->push_back(KeyframeOffset(TimelineOffset::NamedRange::kNone, 1)); |
| stream.ConsumeIncludingWhitespace(); |
| } else { |
| auto* stream_name_percent = To<CSSValueList>( |
| css_parsing_utils::ConsumeTimelineRangeNameAndPercent(stream, |
| *context)); |
| if (!stream_name_percent) { |
| return nullptr; |
| } |
| |
| auto stream_name = To<CSSIdentifierValue>(stream_name_percent->Item(0)) |
| .ConvertTo<TimelineOffset::NamedRange>(); |
| auto percent = |
| To<CSSPrimitiveValue>(stream_name_percent->Item(1)).GetFloatValue(); |
| |
| if (!RuntimeEnabledFeatures::ScrollTimelineEnabled() && |
| stream_name != TimelineOffset::NamedRange::kNone) { |
| return nullptr; |
| } |
| |
| result->push_back(KeyframeOffset(stream_name, percent / 100.0)); |
| } |
| } else { |
| return nullptr; |
| } |
| |
| if (stream.Peek().GetType() != kCommaToken) { |
| return result; |
| } |
| stream.Consume(); |
| } |
| } |
| |
| CSSParserMode CSSParserImpl::GetMode() const { |
| return context_->Mode(); |
| } |
| |
| } // namespace blink |