From 867c33b928f5416a274e993057f508b21fcf901a Mon Sep 17 00:00:00 2001 From: "L. David Baron" Date: Tue, 2 Aug 2022 17:13:50 +0000 Subject: [PATCH] Implement the :toggle() pseudo-class. This implements https://tabatkins.github.io/css-toggle/#checked-pseudoclass (Support for toggles is behind the CSSToggles flag, which is currently off.) Bug: 1250716 Change-Id: I2d3226c5a023e2bf36dd4d990d57933f932983ce Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3780614 Commit-Queue: David Baron Reviewed-by: Anders Hartvoll Ruud Cr-Commit-Position: refs/heads/main@{#1030624} --- .../blink/renderer/core/css/css_selector.cc | 33 +++ .../blink/renderer/core/css/css_selector.h | 10 +- .../core/css/parser/css_parser_selector.h | 4 + .../core/css/parser/css_selector_parser.cc | 35 +++ .../renderer/core/css/rule_feature_set.cc | 2 + .../renderer/core/css/selector_checker.cc | 24 ++ .../core/inspector/inspector_trace_events.cc | 1 + .../blink/renderer/core/style/build.gni | 1 + .../blink/renderer/core/style/toggle_root.cc | 18 ++ .../blink/renderer/core/style/toggle_root.h | 3 + third_party/blink/web_tests/TestExpectations | 1 + .../toggle-pseudo-class.tentative.html | 235 ++++++++++++++++++ 12 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 third_party/blink/renderer/core/style/toggle_root.cc create mode 100644 third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-pseudo-class.tentative.html diff --git a/third_party/blink/renderer/core/css/css_selector.cc b/third_party/blink/renderer/core/css/css_selector.cc index fcd03fcc9444e1..ae5560ef26a63c 100644 --- a/third_party/blink/renderer/core/css/css_selector.cc +++ b/third_party/blink/renderer/core/css/css_selector.cc @@ -323,6 +323,7 @@ PseudoId CSSSelector::GetPseudoId(PseudoType type) { case kPseudoRightPage: case kPseudoInRange: case kPseudoOutOfRange: + case kPseudoToggle: case kPseudoWebKitCustomElement: case kPseudoBlinkInternalElement: case kPseudoCue: @@ -505,6 +506,7 @@ const static NameToPseudoStruct kPseudoTypeWithArgumentsMap[] = { CSSSelector::kPseudoPageTransitionOutgoingImage}, {"part", CSSSelector::kPseudoPart}, {"slotted", CSSSelector::kPseudoSlotted}, + {"toggle", CSSSelector::kPseudoToggle}, {"where", CSSSelector::kPseudoWhere}, }; @@ -586,6 +588,11 @@ CSSSelector::PseudoType CSSSelector::NameToPseudoType(const AtomicString& name, return CSSSelector::kPseudoUnknown; } + if (match->type == CSSSelector::kPseudoToggle && + !RuntimeEnabledFeatures::CSSTogglesEnabled()) { + return CSSSelector::kPseudoUnknown; + } + return static_cast(match->type); } @@ -774,6 +781,7 @@ void CSSSelector::UpdatePseudoType(const AtomicString& value, case kPseudoStart: case kPseudoState: case kPseudoTarget: + case kPseudoToggle: case kPseudoTopLayer: case kPseudoUnknown: case kPseudoValid: @@ -873,6 +881,15 @@ const CSSSelector* CSSSelector::SerializeCompound( SerializeIdentifier(simple_selector->Argument(), builder); builder.Append(')'); break; + case kPseudoToggle: + builder.Append('('); + SerializeIdentifier(simple_selector->Argument(), builder); + if (const ToggleRoot::State* value = simple_selector->ToggleValue()) { + builder.Append(" "); + builder.Append(value->ToString()); + } + builder.Append(')'); + break; case kPseudoHas: case kPseudoNot: DCHECK(simple_selector->SelectorList()); @@ -1051,6 +1068,13 @@ void CSSSelector::SetSelectorList( data_.rare_data_->selector_list_ = std::move(selector_list); } +void CSSSelector::SetToggle(const AtomicString& name, + std::unique_ptr&& value) { + CreateRareData(); + data_.rare_data_->argument_ = name; + data_.rare_data_->toggle_value_ = std::move(value); +} + void CSSSelector::SetContainsPseudoInsideHasPseudoClass() { CreateRareData(); data_.rare_data_->bits_.has_.contains_pseudo_ = true; @@ -1109,6 +1133,15 @@ static bool ValidateSubSelector(const CSSSelector* selector) { case CSSSelector::kPseudoIsHtml: case CSSSelector::kPseudoListBox: case CSSSelector::kPseudoHostHasAppearance: + case CSSSelector::kPseudoToggle: + // TODO(https://crbug.com/1346456): Many pseudos should probably be + // added to this list. The default: case below should also be removed + // so that those adding new pseudos know they need to choose one path or + // the other here. + // + // However, it's not clear why a pseudo should be in one list or the + // other. It's also entirely possible that this entire switch() should + // be removed and all cases should return true. return true; default: return false; diff --git a/third_party/blink/renderer/core/css/css_selector.h b/third_party/blink/renderer/core/css/css_selector.h index efd9e528266056..6085371001b0bb 100644 --- a/third_party/blink/renderer/core/css/css_selector.h +++ b/third_party/blink/renderer/core/css/css_selector.h @@ -30,6 +30,7 @@ #include "third_party/blink/renderer/core/css/parser/css_parser_mode.h" #include "third_party/blink/renderer/core/dom/qualified_name.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" +#include "third_party/blink/renderer/core/style/toggle_root.h" #include "third_party/blink/renderer/platform/wtf/ref_counted.h" namespace blink { @@ -264,6 +265,7 @@ class CORE_EXPORT CSSSelector { kPseudoInRange, kPseudoOutOfRange, kPseudoXrOverlay, + kPseudoToggle, // Pseudo elements in UA ShadowRoots. Available in any stylesheets. kPseudoWebKitCustomElement, // Pseudo elements in UA ShadowRoots. Available only in UA stylesheets. @@ -356,6 +358,9 @@ class CORE_EXPORT CSSSelector { const Vector* PartNames() const { return has_rare_data_ ? data_.rare_data_->part_names_.get() : nullptr; } + const ToggleRoot::State* ToggleValue() const { + return has_rare_data_ ? data_.rare_data_->toggle_value_.get() : nullptr; + } bool ContainsPseudoInsideHasPseudoClass() const { return has_rare_data_ ? data_.rare_data_->bits_.has_.contains_pseudo_ : false; @@ -377,6 +382,8 @@ class CORE_EXPORT CSSSelector { void SetArgument(const AtomicString&); void SetSelectorList(std::unique_ptr); void SetPartNames(std::unique_ptr>); + void SetToggle(const AtomicString& name, + std::unique_ptr&& value); void SetContainsPseudoInsideHasPseudoClass(); void SetContainsComplexLogicalCombinationsInsideHasPseudoClass(); @@ -500,11 +507,12 @@ class CORE_EXPORT CSSSelector { } has_; } bits_; QualifiedName attribute_; // used for attribute selector - AtomicString argument_; // Used for :contains, :lang, :nth-* + AtomicString argument_; // Used for :contains, :lang, :nth-*, :toggle() std::unique_ptr selector_list_; // Used for :-webkit-any and :not std::unique_ptr> part_names_; // Used for ::part() selectors. + std::unique_ptr toggle_value_; // used for :toggle() private: RareData(const AtomicString& value); diff --git a/third_party/blink/renderer/core/css/parser/css_parser_selector.h b/third_party/blink/renderer/core/css/parser/css_parser_selector.h index 979b879e054e63..c16b03dbbaad98 100644 --- a/third_party/blink/renderer/core/css/parser/css_parser_selector.h +++ b/third_party/blink/renderer/core/css/parser/css_parser_selector.h @@ -72,6 +72,10 @@ class CORE_EXPORT CSSParserSelector { selector_->SetRelation(value); } void SetForPage() { selector_->SetForPage(); } + void SetToggle(const AtomicString& name, + std::unique_ptr&& value) { + selector_->SetToggle(name, std::move(value)); + } void UpdatePseudoType(const AtomicString& value, const CSSParserContext& context, diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc index 35fd1bedf7d7d1..51ef80b65305be 100644 --- a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc +++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc @@ -12,6 +12,7 @@ #include "third_party/blink/renderer/core/css/parser/css_parser_context.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/properties/css_parsing_utils.h" #include "third_party/blink/renderer/core/css/style_sheet_contents.h" #include "third_party/blink/renderer/core/dom/pseudo_element.h" #include "third_party/blink/renderer/core/frame/deprecation/deprecation.h" @@ -1051,6 +1052,40 @@ std::unique_ptr CSSSelectorParser::ConsumePseudo( selector->SetArgument(ident.Value().ToAtomicString()); return selector; } + case CSSSelector::kPseudoToggle: { + using State = ToggleRoot::State; + + const CSSParserToken& name = block.ConsumeIncludingWhitespace(); + if (name.GetType() != kIdentToken || + !css_parsing_utils::IsCustomIdent(name.Id())) { + return nullptr; + } + std::unique_ptr value; + if (!block.AtEnd()) { + const CSSParserToken& value_token = block.ConsumeIncludingWhitespace(); + switch (value_token.GetType()) { + case kIdentToken: + if (!css_parsing_utils::IsCustomIdent(value_token.Id())) + return nullptr; + value = + std::make_unique(value_token.Value().ToAtomicString()); + break; + case kNumberToken: + if (value_token.GetNumericValueType() != kIntegerValueType || + value_token.NumericValue() < 0) { + return nullptr; + } + value = std::make_unique(value_token.NumericValue()); + break; + default: + return nullptr; + } + } + if (!block.AtEnd()) + return nullptr; + selector->SetToggle(name.Value().ToAtomicString(), std::move(value)); + return selector; + } default: break; } diff --git a/third_party/blink/renderer/core/css/rule_feature_set.cc b/third_party/blink/renderer/core/css/rule_feature_set.cc index de27af2b6397c9..e8c3e0d0e01045 100644 --- a/third_party/blink/renderer/core/css/rule_feature_set.cc +++ b/third_party/blink/renderer/core/css/rule_feature_set.cc @@ -194,6 +194,7 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) { case CSSSelector::kPseudoPageTransitionImageWrapper: case CSSSelector::kPseudoPageTransitionIncomingImage: case CSSSelector::kPseudoPageTransitionOutgoingImage: + case CSSSelector::kPseudoToggle: return true; case CSSSelector::kPseudoUnknown: case CSSSelector::kPseudoLeftPage: @@ -661,6 +662,7 @@ InvalidationSet* RuleFeatureSet::InvalidationSetForSimpleSelector( case CSSSelector::kPseudoMultiSelectFocus: case CSSSelector::kPseudoModal: case CSSSelector::kPseudoSelectorFragmentAnchor: + case CSSSelector::kPseudoToggle: return &EnsurePseudoInvalidationSet(selector.GetPseudoType(), type, position); case CSSSelector::kPseudoFirstOfType: diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc index 4dbba52ac45076..1d082151222820 100644 --- a/third_party/blink/renderer/core/css/selector_checker.cc +++ b/third_party/blink/renderer/core/css/selector_checker.cc @@ -45,6 +45,7 @@ #include "third_party/blink/renderer/core/dom/popup_data.h" #include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/dom/text.h" +#include "third_party/blink/renderer/core/dom/toggle.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h" @@ -1562,6 +1563,29 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, case CSSSelector::kPseudoRelativeAnchor: DCHECK(context.relative_anchor_element); return context.relative_anchor_element == &element; + case CSSSelector::kPseudoToggle: { + using State = ToggleRoot::State; + + const AtomicString& name = selector.Argument(); + const State* value = selector.ToggleValue(); + + auto [toggle, toggle_element] = element.FindToggleInScope(name); + DCHECK_EQ(!toggle, !toggle_element); + // An element matches :toggle() if the element is in scope for a toggle + // with the name given by , and ... + if (!toggle) + return false; + + if (value) { + // ... either the toggle’s value matches the provided , + // ... + return toggle->ValueMatches(*value); + } else { + // ... or the is omitted and the toggle is in any + // active value. + return !toggle->ValueMatches(State(0)); + } + } case CSSSelector::kPseudoUnknown: default: NOTREACHED(); diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc index 3cba97e6e0424d..04ebc82fc2a999 100644 --- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc +++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc @@ -365,6 +365,7 @@ const char* PseudoTypeToString(CSSSelector::PseudoType pseudo_type) { DEFINE_STRING_MAPPING(PseudoPlaying) DEFINE_STRING_MAPPING(PseudoInRange) DEFINE_STRING_MAPPING(PseudoOutOfRange) + DEFINE_STRING_MAPPING(PseudoToggle) DEFINE_STRING_MAPPING(PseudoWebKitCustomElement) DEFINE_STRING_MAPPING(PseudoBlinkInternalElement) DEFINE_STRING_MAPPING(PseudoCue) diff --git a/third_party/blink/renderer/core/style/build.gni b/third_party/blink/renderer/core/style/build.gni index 7ddde62064d7b3..1d22c964dacc6c 100644 --- a/third_party/blink/renderer/core/style/build.gni +++ b/third_party/blink/renderer/core/style/build.gni @@ -110,6 +110,7 @@ blink_core_sources_style = [ "text_size_adjust.h", "toggle_group.h", "toggle_group_list.h", + "toggle_root.cc", "toggle_root.h", "toggle_root_list.h", "toggle_trigger.h", diff --git a/third_party/blink/renderer/core/style/toggle_root.cc b/third_party/blink/renderer/core/style/toggle_root.cc new file mode 100644 index 00000000000000..12b5cf14fc3f7f --- /dev/null +++ b/third_party/blink/renderer/core/style/toggle_root.cc @@ -0,0 +1,18 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/style/toggle_root.h" + +namespace blink { + +String ToggleRoot::State::ToString() const { + switch (GetType()) { + case Type::Integer: + return String::Number(AsInteger()); + case Type::Name: + return AsName().GetString(); + } +} + +} // namespace blink diff --git a/third_party/blink/renderer/core/style/toggle_root.h b/third_party/blink/renderer/core/style/toggle_root.h index 9b04ce73d07e41..621918090e0fba 100644 --- a/third_party/blink/renderer/core/style/toggle_root.h +++ b/third_party/blink/renderer/core/style/toggle_root.h @@ -9,6 +9,7 @@ #include "third_party/abseil-cpp/absl/types/variant.h" #include "third_party/blink/renderer/core/style/toggle_group.h" #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/vector.h" namespace blink { @@ -82,6 +83,8 @@ class ToggleRoot { return absl::get(value_); } + String ToString() const; + private: absl::variant value_; }; diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 3458458b2638bd..650716a21956f0 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations @@ -5047,6 +5047,7 @@ crbug.com/1008483 virtual/backface-visibility-interop/paint/invalidation/multico crbug.com/1250716 external/wpt/css/css-toggle/toggle-creation.tentative.html [ Failure ] crbug.com/1250716 external/wpt/css/css-toggle/toggle-activation.tentative.html [ Failure ] crbug.com/1250716 external/wpt/css/css-toggle/toggle-activation-with-groups.tentative.html [ Failure ] +crbug.com/1250716 external/wpt/css/css-toggle/toggle-pseudo-class.tentative.html [ Failure ] # Swiftshader issue. crbug.com/1048149 external/wpt/html/browsers/the-window-object/open-close/open-features-non-integer-innerwidth.html [ Crash Timeout ] diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-pseudo-class.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-pseudo-class.tentative.html new file mode 100644 index 00000000000000..7fb5c26d91b847 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-pseudo-class.tentative.html @@ -0,0 +1,235 @@ + + +CSS Toggles: creation of toggles + + + + + + + + + + +
+ +