From 1e31a04af458e4a319e820fa5005c4068a72cee4 Mon Sep 17 00:00:00 2001
From: David Shin <dshin@mozilla.com>
Date: Tue, 19 Mar 2024 13:36:48 +0000
Subject: [PATCH] Bug 1882581: Implement `@scope` parsing.
 r=firefox-style-system-reviewers,saschanaz,emilio

Differential Revision: https://phabricator.services.mozilla.com/D203153
---
 dom/webidl/CSSScopeRule.webidl                |  14 ++
 dom/webidl/moz.build                          |   1 +
 layout/inspector/InspectorUtils.cpp           |   1 +
 layout/inspector/ServoStyleRuleMap.cpp        |   6 +-
 layout/style/CSSScopeRule.cpp                 |  66 +++++++
 layout/style/CSSScopeRule.h                   |  49 ++++++
 layout/style/Rule.cpp                         |  14 +-
 layout/style/ServoBindingTypes.h              |   1 +
 layout/style/ServoBindings.h                  |   1 +
 layout/style/ServoCSSRuleList.cpp             |   3 +
 layout/style/ServoStyleConstsInlines.h        |   1 +
 layout/style/ServoStyleSet.cpp                |   2 +
 layout/style/moz.build                        |   2 +
 modules/libpref/init/StaticPrefList.yaml      |   7 +
 servo/components/selectors/parser.rs          |  17 ++
 servo/components/style/gecko/arc_types.rs     |   7 +-
 .../style/invalidation/stylesheets.rs         |   5 +
 servo/components/style/stylesheets/mod.rs     |  12 ++
 .../style/stylesheets/rule_parser.rs          |  21 ++-
 .../style/stylesheets/rules_iterator.rs       |   1 +
 .../style/stylesheets/scope_rule.rs           | 162 ++++++++++++++++++
 .../style/stylesheets/stylesheet.rs           |   5 +-
 servo/components/style/stylist.rs             |   3 +-
 servo/ports/geckolib/glue.rs                  |  28 ++-
 startupcache/StartupCache.h                   |   2 +-
 .../meta/css/css-cascade/__dir__.ini          |   2 +-
 .../css/css-cascade/at-scope-parsing.html.ini |  80 ---------
 .../meta/css/css-cascade/idlharness.html.ini  |  35 ----
 .../meta/css/css-cascade/scope-cssom.html.ini |  39 -----
 .../meta/css/css-cascade/scope-deep.html.ini  |   5 -
 .../css/css-cascade/scope-evaluation.html.ini |  15 +-
 .../css/css-cascade/scope-implicit.html.ini   |   6 +
 .../scope-name-defining-rules.html.ini        |  12 --
 .../css/css-cascade/scope-nesting.html.ini    |   3 +
 .../css/css-cascade/scope-proximity.html.ini  |   3 -
 .../scope-shadow.tentative.html.ini           |   3 -
 .../css-cascade/scope-visited-cssom.html.ini  |  18 +-
 .../css-nesting/conditional-rules.html.ini    |   3 +
 .../css/css-cascade/at-scope-parsing.html     |   1 +
 39 files changed, 448 insertions(+), 208 deletions(-)
 create mode 100644 dom/webidl/CSSScopeRule.webidl
 create mode 100644 layout/style/CSSScopeRule.cpp
 create mode 100644 layout/style/CSSScopeRule.h
 create mode 100644 servo/components/style/stylesheets/scope_rule.rs
 delete mode 100644 testing/web-platform/meta/css/css-cascade/at-scope-parsing.html.ini
 delete mode 100644 testing/web-platform/meta/css/css-cascade/idlharness.html.ini
 delete mode 100644 testing/web-platform/meta/css/css-cascade/scope-cssom.html.ini
 delete mode 100644 testing/web-platform/meta/css/css-cascade/scope-deep.html.ini
 delete mode 100644 testing/web-platform/meta/css/css-cascade/scope-name-defining-rules.html.ini
 create mode 100644 testing/web-platform/meta/css/css-nesting/conditional-rules.html.ini

diff --git a/dom/webidl/CSSScopeRule.webidl b/dom/webidl/CSSScopeRule.webidl
new file mode 100644
index 0000000000000..009209beaf5b1
--- /dev/null
+++ b/dom/webidl/CSSScopeRule.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://drafts.csswg.org/css-cascade-6/#the-cssscoperule-interface
+ */
+
+[Exposed=Window, Pref="layout.css.at-scope.enabled"]
+interface CSSScopeRule : CSSGroupingRule {
+  readonly attribute UTF8String? start;
+  readonly attribute UTF8String? end;
+};
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index 3880b727e731d..ae65ab1b00602 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -492,6 +492,7 @@ WEBIDL_FILES = [
     "CSSPseudoElement.webidl",
     "CSSRule.webidl",
     "CSSRuleList.webidl",
+    "CSSScopeRule.webidl",
     "CSSStyleDeclaration.webidl",
     "CSSStyleRule.webidl",
     "CSSStyleSheet.webidl",
diff --git a/layout/inspector/InspectorUtils.cpp b/layout/inspector/InspectorUtils.cpp
index f1886a0dd0549..80b93029e5421 100644
--- a/layout/inspector/InspectorUtils.cpp
+++ b/layout/inspector/InspectorUtils.cpp
@@ -433,6 +433,7 @@ static uint32_t CollectAtRules(ServoCSSRuleList& aRuleList,
       case StyleCssRuleType::CounterStyle:
       case StyleCssRuleType::FontFeatureValues:
       case StyleCssRuleType::FontPaletteValues:
+      case StyleCssRuleType::Scope:
         break;
     }
 
diff --git a/layout/inspector/ServoStyleRuleMap.cpp b/layout/inspector/ServoStyleRuleMap.cpp
index b67d0c98fc17a..e47855ff1a971 100644
--- a/layout/inspector/ServoStyleRuleMap.cpp
+++ b/layout/inspector/ServoStyleRuleMap.cpp
@@ -86,7 +86,8 @@ void ServoStyleRuleMap::RuleRemoved(StyleSheet& aStyleSheet,
     case StyleCssRuleType::Supports:
     case StyleCssRuleType::LayerBlock:
     case StyleCssRuleType::Container:
-    case StyleCssRuleType::Document: {
+    case StyleCssRuleType::Document:
+    case StyleCssRuleType::Scope: {
       // See the comment in SheetRemoved.
       mTable.Clear();
       break;
@@ -124,7 +125,8 @@ void ServoStyleRuleMap::FillTableFromRule(css::Rule& aRule) {
     case StyleCssRuleType::Media:
     case StyleCssRuleType::Supports:
     case StyleCssRuleType::Container:
-    case StyleCssRuleType::Document: {
+    case StyleCssRuleType::Document:
+    case StyleCssRuleType::Scope: {
       auto& rule = static_cast<css::GroupRule&>(aRule);
       FillTableFromRuleList(*rule.CssRules());
       break;
diff --git a/layout/style/CSSScopeRule.cpp b/layout/style/CSSScopeRule.cpp
new file mode 100644
index 0000000000000..18047a582b670
--- /dev/null
+++ b/layout/style/CSSScopeRule.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CSSScopeRule.h"
+#include "mozilla/dom/CSSScopeRuleBinding.h"
+#include "mozilla/ServoBindings.h"
+
+namespace mozilla::dom {
+
+CSSScopeRule::CSSScopeRule(RefPtr<StyleScopeRule> aRawRule, StyleSheet* aSheet,
+                           css::Rule* aParentRule, uint32_t aLine,
+                           uint32_t aColumn)
+    : css::GroupRule(aSheet, aParentRule, aLine, aColumn),
+      mRawRule(std::move(aRawRule)) {}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(CSSScopeRule, css::GroupRule)
+
+// QueryInterface implementation for SupportsRule
+
+#ifdef DEBUG
+void CSSScopeRule::List(FILE* out, int32_t aIndent) const {
+  nsAutoCString str;
+  for (int32_t i = 0; i < aIndent; i++) {
+    str.AppendLiteral("  ");
+  }
+  Servo_ScopeRule_Debug(mRawRule, &str);
+  fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+StyleCssRuleType CSSScopeRule::Type() const { return StyleCssRuleType::Scope; }
+
+already_AddRefed<StyleLockedCssRules> CSSScopeRule::GetOrCreateRawRules() {
+  return Servo_ScopeRule_GetRules(mRawRule).Consume();
+}
+
+void CSSScopeRule::SetRawAfterClone(RefPtr<StyleScopeRule> aRaw) {
+  mRawRule = std::move(aRaw);
+  css::GroupRule::DidSetRawAfterClone();
+}
+
+void CSSScopeRule::GetCssText(nsACString& aCssText) const {
+  Servo_ScopeRule_GetCssText(mRawRule.get(), &aCssText);
+}
+
+void CSSScopeRule::GetStart(nsACString& aStart) const {
+  Servo_ScopeRule_GetStart(mRawRule.get(), &aStart);
+}
+
+void CSSScopeRule::GetEnd(nsACString& aEnd) const {
+  Servo_ScopeRule_GetEnd(mRawRule.get(), &aEnd);
+}
+
+size_t CSSScopeRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+  return aMallocSizeOf(this);
+}
+
+JSObject* CSSScopeRule::WrapObject(JSContext* aCx,
+                                   JS::Handle<JSObject*> aGivenProto) {
+  return CSSScopeRule_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+}  // namespace mozilla::dom
diff --git a/layout/style/CSSScopeRule.h b/layout/style/CSSScopeRule.h
new file mode 100644
index 0000000000000..2cb1fa7e4057d
--- /dev/null
+++ b/layout/style/CSSScopeRule.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CSSScopeRule_h___
+#define CSSScopeRule_h___
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla::dom {
+
+class CSSScopeRule final : public css::GroupRule {
+ public:
+  CSSScopeRule(RefPtr<StyleScopeRule> aRawRule, StyleSheet* aSheet,
+               css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn);
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+#ifdef DEBUG
+  void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+
+  StyleScopeRule* Raw() const { return mRawRule; }
+  void SetRawAfterClone(RefPtr<StyleScopeRule>);
+
+  already_AddRefed<StyleLockedCssRules> GetOrCreateRawRules() final;
+
+  // WebIDL interface
+  StyleCssRuleType Type() const final;
+  void GetCssText(nsACString& aCssText) const final;
+
+  void GetStart(nsACString& aStart) const;
+  void GetEnd(nsACString& aEnd) const;
+
+  size_t SizeOfIncludingThis(MallocSizeOf) const override;
+  JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
+
+ private:
+  ~CSSScopeRule() = default;
+
+  RefPtr<StyleScopeRule> mRawRule;
+};
+
+}  // namespace mozilla::dom
+
+#endif
diff --git a/layout/style/Rule.cpp b/layout/style/Rule.cpp
index 0a7de42789055..1bba782d65039 100644
--- a/layout/style/Rule.cpp
+++ b/layout/style/Rule.cpp
@@ -100,13 +100,13 @@ void Rule::AssertParentRuleType() {
   // this->Type() here since it's virtual.
   if (mParentRule) {
     auto type = mParentRule->Type();
-    MOZ_ASSERT(type == StyleCssRuleType::Media ||
-               type == StyleCssRuleType::Style ||
-               type == StyleCssRuleType::Document ||
-               type == StyleCssRuleType::Supports ||
-               type == StyleCssRuleType::Keyframes ||
-               type == StyleCssRuleType::LayerBlock ||
-               type == StyleCssRuleType::Container);
+    MOZ_ASSERT(
+        type == StyleCssRuleType::Media || type == StyleCssRuleType::Style ||
+        type == StyleCssRuleType::Document ||
+        type == StyleCssRuleType::Supports ||
+        type == StyleCssRuleType::Keyframes ||
+        type == StyleCssRuleType::LayerBlock ||
+        type == StyleCssRuleType::Container || type == StyleCssRuleType::Scope);
   }
 }
 #endif
diff --git a/layout/style/ServoBindingTypes.h b/layout/style/ServoBindingTypes.h
index fe4f96cf0b5be..196466be1d607 100644
--- a/layout/style/ServoBindingTypes.h
+++ b/layout/style/ServoBindingTypes.h
@@ -129,6 +129,7 @@ UNLOCKED_RULE_TYPE(Supports)
 UNLOCKED_RULE_TYPE(Document)
 UNLOCKED_RULE_TYPE(FontFeatureValues)
 UNLOCKED_RULE_TYPE(FontPaletteValues)
+UNLOCKED_RULE_TYPE(Scope)
 
 SERVO_ARC_TYPE(AnimationValue, mozilla::StyleAnimationValue)
 SERVO_ARC_TYPE(ComputedStyle, mozilla::ComputedStyle)
diff --git a/layout/style/ServoBindings.h b/layout/style/ServoBindings.h
index 6f1900651299f..44dfd544236b0 100644
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -87,6 +87,7 @@ BASIC_RULE_FUNCS_UNLOCKED(FontPaletteValues)
 BASIC_RULE_FUNCS_LOCKED(FontFace)
 BASIC_RULE_FUNCS_LOCKED(CounterStyle)
 GROUP_RULE_FUNCS_UNLOCKED(Container)
+GROUP_RULE_FUNCS_UNLOCKED(Scope)
 
 #undef GROUP_RULE_FUNCS_LOCKED
 #undef GROUP_RULE_FUNCS_UNLOCKED
diff --git a/layout/style/ServoCSSRuleList.cpp b/layout/style/ServoCSSRuleList.cpp
index 6fcdfdd4b57ae..e26305c9932aa 100644
--- a/layout/style/ServoCSSRuleList.cpp
+++ b/layout/style/ServoCSSRuleList.cpp
@@ -24,6 +24,7 @@
 #include "mozilla/dom/CSSPropertyRule.h"
 #include "mozilla/dom/CSSStyleRule.h"
 #include "mozilla/dom/CSSSupportsRule.h"
+#include "mozilla/dom/CSSScopeRule.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StyleSheet.h"
@@ -98,6 +99,7 @@ css::Rule* ServoCSSRuleList::GetRule(uint32_t aIndex) {
       CASE_RULE_UNLOCKED(LayerBlock, LayerBlock)
       CASE_RULE_UNLOCKED(LayerStatement, LayerStatement)
       CASE_RULE_UNLOCKED(Container, Container)
+      CASE_RULE_UNLOCKED(Scope, Scope)
 #undef CASE_RULE_LOCKED
 #undef CASE_RULE_UNLOCKED
 #undef CASE_RULE_WITH_PREFIX
@@ -276,6 +278,7 @@ void ServoCSSRuleList::SetRawContents(RefPtr<StyleLockedCssRules> aNewRules,
       RULE_CASE_UNLOCKED(LayerBlock, LayerBlock)
       RULE_CASE_UNLOCKED(LayerStatement, LayerStatement)
       RULE_CASE_UNLOCKED(Container, Container)
+      RULE_CASE_UNLOCKED(Scope, Scope)
       case StyleCssRuleType::Keyframe:
         MOZ_ASSERT_UNREACHABLE("keyframe rule cannot be here");
         break;
diff --git a/layout/style/ServoStyleConstsInlines.h b/layout/style/ServoStyleConstsInlines.h
index aec70cea8fe58..a30416e80d5e1 100644
--- a/layout/style/ServoStyleConstsInlines.h
+++ b/layout/style/ServoStyleConstsInlines.h
@@ -53,6 +53,7 @@ template struct StyleStrong<StyleFontPaletteValuesRule>;
 template struct StyleStrong<StyleLockedFontFaceRule>;
 template struct StyleStrong<StyleLockedCounterStyleRule>;
 template struct StyleStrong<StyleContainerRule>;
+template struct StyleStrong<StyleScopeRule>;
 
 template <typename T>
 inline void StyleOwnedSlice<T>::Clear() {
diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp
index cfc38849b8119..87d16ec9346ff 100644
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -41,6 +41,7 @@
 #include "mozilla/dom/CSSNamespaceRule.h"
 #include "mozilla/dom/CSSPageRule.h"
 #include "mozilla/dom/CSSPropertyRule.h"
+#include "mozilla/dom/CSSScopeRule.h"
 #include "mozilla/dom/CSSSupportsRule.h"
 #include "mozilla/dom/FontFaceSet.h"
 #include "mozilla/dom/Element.h"
@@ -1002,6 +1003,7 @@ void ServoStyleSet::RuleChangedInternal(StyleSheet& aSheet, css::Rule& aRule,
     CASE_FOR(LayerBlock, LayerBlock)
     CASE_FOR(LayerStatement, LayerStatement)
     CASE_FOR(Container, Container)
+    CASE_FOR(Scope, Scope)
     // @namespace can only be inserted / removed when there are only other
     // @namespace and @import rules, and can't be mutated.
     case StyleCssRuleType::Namespace:
diff --git a/layout/style/moz.build b/layout/style/moz.build
index a14ab6a7ac8e6..36e63fe87e49c 100644
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -143,6 +143,7 @@ EXPORTS.mozilla.dom += [
     "CSSPageRule.h",
     "CSSPropertyRule.h",
     "CSSRuleList.h",
+    "CSSScopeRule.h",
     "CSSStyleRule.h",
     "CSSSupportsRule.h",
     "CSSValue.h",
@@ -194,6 +195,7 @@ UNIFIED_SOURCES += [
     "CSSPageRule.cpp",
     "CSSPropertyRule.cpp",
     "CSSRuleList.cpp",
+    "CSSScopeRule.cpp",
     "CSSStyleRule.cpp",
     "CSSSupportsRule.cpp",
     "DeclarationBlock.cpp",
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index f790d856095e5..a73b73b7e827e 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -8752,6 +8752,13 @@
   mirror: always
   rust: true
 
+# Whether @scope rule is enabled
+- name: layout.css.at-scope.enabled
+  type: RelaxedAtomicBool
+  value: false
+  mirror: always
+  rust: true
+
 # Dictates whether or not the prefers contrast media query will be
 # usable.
 #   true: prefers-contrast will toggle based on OS and browser settings.
diff --git a/servo/components/selectors/parser.rs b/servo/components/selectors/parser.rs
index 9b0acb0671d77..ca3a5c76f4f25 100644
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -469,6 +469,23 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
         )
     }
 
+    pub fn parse_forgiving<'i, 't, P>(
+        parser: &P,
+        input: &mut CssParser<'i, 't>,
+        parse_relative: ParseRelative,
+    ) -> Result<Self, ParseError<'i, P::Error>>
+    where
+        P: Parser<'i, Impl = Impl>,
+    {
+        Self::parse_with_state(
+            parser,
+            input,
+            SelectorParsingState::empty(),
+            ForgivingParsing::Yes,
+            parse_relative,
+        )
+    }
+
     #[inline]
     fn parse_with_state<'i, 't, P>(
         parser: &P,
diff --git a/servo/components/style/gecko/arc_types.rs b/servo/components/style/gecko/arc_types.rs
index 24bf22d69a721..e4300be6e46c8 100644
--- a/servo/components/style/gecko/arc_types.rs
+++ b/servo/components/style/gecko/arc_types.rs
@@ -16,7 +16,7 @@ use crate::stylesheets::keyframes_rule::Keyframe;
 use crate::stylesheets::{
     ContainerRule, CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule,
     FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule,
-    MediaRule, NamespaceRule, PageRule, PropertyRule, StyleRule, StylesheetContents, SupportsRule,
+    MediaRule, NamespaceRule, PageRule, PropertyRule, ScopeRule, StyleRule, StylesheetContents, SupportsRule,
 };
 use servo_arc::Arc;
 
@@ -169,3 +169,8 @@ impl_simple_arc_ffi!(
     Servo_AnimationValue_AddRef,
     Servo_AnimationValue_Release
 );
+impl_simple_arc_ffi!(
+    ScopeRule,
+    Servo_ScopeRule_AddRef,
+    Servo_ScopeRule_Release
+);
diff --git a/servo/components/style/invalidation/stylesheets.rs b/servo/components/style/invalidation/stylesheets.rs
index d845897aa44ee..82b87dc904383 100644
--- a/servo/components/style/invalidation/stylesheets.rs
+++ b/servo/components/style/invalidation/stylesheets.rs
@@ -646,6 +646,11 @@ impl StylesheetInvalidationSet {
                 debug!(" > Found unsupported rule, marking the whole subtree invalid.");
                 self.invalidate_fully();
             },
+            Scope(..) => {
+                // Addition/removal of @scope requires re-evaluation of scope proximity to properly
+                // figure out the styling order.
+                self.invalidate_fully();
+            },
         }
     }
 }
diff --git a/servo/components/style/stylesheets/mod.rs b/servo/components/style/stylesheets/mod.rs
index 2bf75565deddd..ad2b2fadefb33 100644
--- a/servo/components/style/stylesheets/mod.rs
+++ b/servo/components/style/stylesheets/mod.rs
@@ -26,6 +26,7 @@ mod rules_iterator;
 mod style_rule;
 mod stylesheet;
 pub mod supports_rule;
+mod scope_rule;
 
 #[cfg(feature = "gecko")]
 use crate::gecko_bindings::sugar::refptr::RefCounted;
@@ -70,6 +71,7 @@ pub use self::rules_iterator::{
     EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator,
 };
 pub use self::style_rule::StyleRule;
+pub use self::scope_rule::ScopeRule;
 pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind};
 pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet};
 pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets};
@@ -265,6 +267,7 @@ pub enum CssRule {
     Document(Arc<DocumentRule>),
     LayerBlock(Arc<LayerBlockRule>),
     LayerStatement(Arc<LayerStatementRule>),
+    Scope(Arc<ScopeRule>),
 }
 
 impl CssRule {
@@ -311,6 +314,9 @@ impl CssRule {
             },
             // TODO(emilio): Add memory reporting for these rules.
             CssRule::LayerBlock(_) | CssRule::LayerStatement(_) => 0,
+            CssRule::Scope(ref rule) => {
+                rule.unconditional_shallow_size_of(ops) + rule.size_of(guard, ops)
+            }
         }
     }
 }
@@ -349,6 +355,7 @@ pub enum CssRuleType {
     FontPaletteValues = 19,
     // 20 is an arbitrary number to use for Property.
     Property = 20,
+    Scope = 21,
 }
 
 impl CssRuleType {
@@ -436,6 +443,7 @@ impl CssRule {
             CssRule::LayerBlock(_) => CssRuleType::LayerBlock,
             CssRule::LayerStatement(_) => CssRuleType::LayerStatement,
             CssRule::Container(_) => CssRuleType::Container,
+            CssRule::Scope(_) => CssRuleType::Scope,
         }
     }
 
@@ -567,6 +575,9 @@ impl DeepCloneWithLock for CssRule {
             CssRule::LayerBlock(ref arc) => {
                 CssRule::LayerBlock(Arc::new(arc.deep_clone_with_lock(lock, guard, params)))
             },
+            CssRule::Scope(ref arc) => {
+                CssRule::Scope(Arc::new(arc.deep_clone_with_lock(lock, guard, params)))
+            }
         }
     }
 }
@@ -592,6 +603,7 @@ impl ToCssWithGuard for CssRule {
             CssRule::LayerBlock(ref rule) => rule.to_css(guard, dest),
             CssRule::LayerStatement(ref rule) => rule.to_css(guard, dest),
             CssRule::Container(ref rule) => rule.to_css(guard, dest),
+            CssRule::Scope(ref rule) => rule.to_css(guard, dest),
         }
     }
 }
diff --git a/servo/components/style/stylesheets/rule_parser.rs b/servo/components/style/stylesheets/rule_parser.rs
index 742ad5d2501ee..edc2262d357e9 100644
--- a/servo/components/style/stylesheets/rule_parser.rs
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -24,6 +24,7 @@ use crate::stylesheets::import_rule::{ImportLayer, ImportRule, ImportSupportsCon
 use crate::stylesheets::keyframes_rule::parse_keyframe_list;
 use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule};
 use crate::stylesheets::supports_rule::SupportsCondition;
+use crate::stylesheets::scope_rule::{ScopeBounds, ScopeRule};
 use crate::stylesheets::{
     AllowImportRules, CorsMode, CssRule, CssRuleType, CssRuleTypes, CssRules, DocumentRule,
     FontFeatureValuesRule, FontPaletteValuesRule, KeyframesRule, MarginRule, MarginRuleType,
@@ -231,6 +232,8 @@ pub enum AtRulePrelude {
     Namespace(Option<Prefix>, Namespace),
     /// A @layer rule prelude.
     Layer(Vec<LayerName>),
+    /// A @scope rule prelude.
+    Scope(ScopeBounds),
 }
 
 impl AtRulePrelude {
@@ -251,6 +254,7 @@ impl AtRulePrelude {
             Self::Margin(..) => "margin",
             Self::Namespace(..) => "namespace",
             Self::Layer(..) => "layer",
+            Self::Scope(..) => "scope",
         }
     }
 }
@@ -507,7 +511,8 @@ impl<'a, 'i> NestedRuleParser<'a, 'i> {
             AtRulePrelude::Supports(..) |
             AtRulePrelude::Container(..) |
             AtRulePrelude::Document(..) |
-            AtRulePrelude::Layer(..) => true,
+            AtRulePrelude::Layer(..) |
+            AtRulePrelude::Scope(..) => true,
 
             AtRulePrelude::Namespace(..) |
             AtRulePrelude::FontFace |
@@ -701,6 +706,10 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> {
                 let cond = DocumentCondition::parse(&self.context, input)?;
                 AtRulePrelude::Document(cond)
             },
+            "scope" if static_prefs::pref!("layout.css.at-scope.enabled") => {
+                let bounds = ScopeBounds::parse(&self.context, input, self.in_style_rule());
+                AtRulePrelude::Scope(bounds)
+            },
             _ => {
                 if static_prefs::pref!("layout.css.margin-rules.enabled") {
                     if let Some(margin_rule_type) = MarginRuleType::match_name(&name) {
@@ -867,6 +876,16 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> {
                 // These rules don't have blocks.
                 return Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock));
             },
+            AtRulePrelude::Scope(bounds) => {
+                let source_location = start.source_location();
+                CssRule::Scope(Arc::new(ScopeRule {
+                    bounds,
+                    rules: self
+                        .parse_nested(input, CssRuleType::Scope)
+                        .into_rules(self.shared_lock, source_location),
+                    source_location,
+                }))
+            },
         };
         self.rules.push(rule);
         Ok(())
diff --git a/servo/components/style/stylesheets/rules_iterator.rs b/servo/components/style/stylesheets/rules_iterator.rs
index 76d41c8184f5a..1ad95cfdc3f80 100644
--- a/servo/components/style/stylesheets/rules_iterator.rs
+++ b/servo/components/style/stylesheets/rules_iterator.rs
@@ -116,6 +116,7 @@ where
                 Some(supports_rule.rules.read_with(guard).0.iter())
             },
             CssRule::LayerBlock(ref layer_rule) => Some(layer_rule.rules.read_with(guard).0.iter()),
+            CssRule::Scope(ref rule) => Some(rule.rules.read_with(guard).0.iter())
         }
     }
 }
diff --git a/servo/components/style/stylesheets/scope_rule.rs b/servo/components/style/stylesheets/scope_rule.rs
new file mode 100644
index 0000000000000..2db4919065e23
--- /dev/null
+++ b/servo/components/style/stylesheets/scope_rule.rs
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A [`@scope`][scope] rule.
+//!
+//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles
+
+use crate::parser::ParserContext;
+use crate::selector_parser::{SelectorImpl, SelectorParser};
+use crate::shared_lock::{
+    DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
+};
+use crate::str::CssStringWriter;
+use crate::stylesheets::CssRules;
+use cssparser::{Parser, SourceLocation, ToCss};
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalSizeOf, MallocUnconditionalShallowSizeOf};
+use selectors::parser::{ParseRelative, SelectorList};
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+use style_traits::CssWriter;
+
+/// A scoped rule.
+#[derive(Debug, ToShmem)]
+pub struct ScopeRule {
+    /// Bounds at which this rule applies.
+    pub bounds: ScopeBounds,
+    /// The nested rules inside the block.
+    pub rules: Arc<Locked<CssRules>>,
+    /// The source position where this rule was found.
+    pub source_location: SourceLocation,
+}
+
+impl DeepCloneWithLock for ScopeRule {
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard,
+        params: &DeepCloneParams,
+    ) -> Self {
+        let rules = self.rules.read_with(guard);
+        Self {
+            bounds: self.bounds.clone(),
+            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
+            source_location: self.source_location.clone(),
+        }
+    }
+}
+
+impl ToCssWithGuard for ScopeRule {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+        dest.write_str("@scope")?;
+        {
+            let mut writer = CssWriter::new(dest);
+            if let Some(start) = self.bounds.start.as_ref() {
+                writer.write_str(" (")?;
+                start.to_css(&mut writer)?;
+                writer.write_char(')')?;
+            }
+            if let Some(end) = self.bounds.end.as_ref() {
+                writer.write_str(" to (")?;
+                end.to_css(&mut writer)?;
+                writer.write_char(')')?;
+            }
+        }
+        self.rules.read_with(guard).to_css_block(guard, dest)
+    }
+}
+
+impl ScopeRule {
+    /// Measure heap usage.
+    #[cfg(feature = "gecko")]
+    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+        self.rules.unconditional_shallow_size_of(ops) +
+            self.rules.read_with(guard).size_of(guard, ops) +
+            self.bounds.size_of(ops)
+    }
+}
+
+/// Bounds of the scope.
+#[derive(Debug, Clone, ToShmem)]
+pub struct ScopeBounds {
+    /// Start of the scope.
+    pub start: Option<SelectorList<SelectorImpl>>,
+    /// End of the scope.
+    pub end: Option<SelectorList<SelectorImpl>>,
+}
+
+impl ScopeBounds {
+    #[cfg(feature = "gecko")]
+    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        fn bound_size_of(bound: &Option<SelectorList<SelectorImpl>>, ops: &mut MallocSizeOfOps) -> usize {
+            bound.as_ref().map(|list| list.unconditional_size_of(ops)).unwrap_or(0)
+        }
+        bound_size_of(&self.start, ops) + bound_size_of(&self.end, ops)
+    }
+}
+
+fn parse_scope<'a>(
+    context: &ParserContext,
+    input: &mut Parser<'a, '_>,
+    in_style_rule: bool,
+    for_end: bool
+) -> Option<SelectorList<SelectorImpl>> {
+    input.try_parse(|input| {
+        if for_end {
+            input.expect_ident_matching("to")?;
+        }
+        input.expect_parenthesis_block()?;
+        input.parse_nested_block(|input| {
+            if input.is_exhausted() {
+                return Ok(None);
+            }
+            let selector_parser = SelectorParser {
+                stylesheet_origin: context.stylesheet_origin,
+                namespaces: &context.namespaces,
+                url_data: context.url_data,
+                for_supports_rule: false,
+            };
+            let parse_relative = if for_end {
+                // TODO(dshin): scope-end can be a relative selector, with the anchor being `:scope`.
+                ParseRelative::No
+            } else if in_style_rule {
+                ParseRelative::ForNesting
+            } else {
+                ParseRelative::No
+            };
+            Ok(Some(SelectorList::parse_forgiving(
+                &selector_parser,
+                input,
+                parse_relative,
+            )?))
+        })
+    })
+    .ok()
+    .flatten()
+}
+
+impl ScopeBounds {
+    /// Parse a container condition.
+    pub fn parse<'a>(
+        context: &ParserContext,
+        input: &mut Parser<'a, '_>,
+        in_style_rule: bool,
+    ) -> Self {
+        let start = parse_scope(
+            context,
+            input,
+            in_style_rule,
+            false
+        );
+
+        let end = parse_scope(
+            context,
+            input,
+            in_style_rule,
+            true
+        );
+        Self { start, end }
+    }
+}
diff --git a/servo/components/style/stylesheets/stylesheet.rs b/servo/components/style/stylesheets/stylesheet.rs
index 1604022871d80..a65b3f7484411 100644
--- a/servo/components/style/stylesheets/stylesheet.rs
+++ b/servo/components/style/stylesheets/stylesheet.rs
@@ -333,7 +333,10 @@ impl SanitizationKind {
             // TODO(emilio): Perhaps Layer should not be always sanitized? But
             // we sanitize @media and co, so this seems safer for now.
             CssRule::LayerStatement(..) |
-            CssRule::LayerBlock(..) => false,
+            CssRule::LayerBlock(..) |
+            // TODO(dshin): Same comment as Layer applies - shouldn't give away
+            // something like display size - erring on the side of "safe" for now.
+            CssRule::Scope(..) => false,
 
             CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true,
 
diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs
index cc1cb75689123..a291c7aab51a2 100644
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -3234,7 +3234,8 @@ impl CascadeData {
                 CssRule::LayerBlock(..) |
                 CssRule::LayerStatement(..) |
                 CssRule::FontPaletteValues(..) |
-                CssRule::FontFeatureValues(..) => {
+                CssRule::FontFeatureValues(..) |
+                CssRule::Scope(..) => {
                     // Not affected by device changes.
                     continue;
                 },
diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs
index 6cb87c38bfc9b..ed979b4492744 100644
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -139,7 +139,7 @@ use style::stylesheets::{
     FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule,
     MediaRule, NamespaceRule, Origin, OriginSet, PagePseudoClassFlags, PageRule, PropertyRule,
     SanitizationData, SanitizationKind, StyleRule, StylesheetContents,
-    StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData,
+    StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData, ScopeRule,
 };
 use style::stylist::{add_size_of_ua_cache, AuthorStylesEnabled, RuleInclusion, Stylist};
 use style::thread_state;
@@ -2483,6 +2483,14 @@ impl_basic_rule_funcs! { (CounterStyle, CounterStyleRule, Locked<CounterStyleRul
     changed: Servo_StyleSet_CounterStyleRuleChanged,
 }
 
+impl_group_rule_funcs! { (Scope, ScopeRule, ScopeRule),
+    get_rules: Servo_ScopeRule_GetRules,
+    getter: Servo_CssRules_GetScopeRuleAt,
+    debug: Servo_ScopeRule_Debug,
+    to_css: Servo_ScopeRule_GetCssText,
+    changed: Servo_StyleSet_ScopeRuleChanged,
+}
+
 #[no_mangle]
 pub extern "C" fn Servo_StyleRule_GetStyle(
     rule: &LockedStyleRule,
@@ -8644,6 +8652,24 @@ pub extern "C" fn Servo_LayerBlockRule_GetName(rule: &LayerBlockRule, result: &m
     }
 }
 
+#[no_mangle]
+pub extern "C" fn Servo_ScopeRule_GetStart(rule: &ScopeRule, result: &mut nsACString) {
+    if let Some(v) = rule.bounds.start.as_ref() {
+        v.to_css(&mut CssWriter::new(result)).unwrap();
+    } else {
+        result.set_is_void(true);
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn Servo_ScopeRule_GetEnd(rule: &ScopeRule, result: &mut nsACString) {
+    if let Some(v) = rule.bounds.end.as_ref() {
+        v.to_css(&mut CssWriter::new(result)).unwrap();
+    } else {
+        result.set_is_void(true);
+    }
+}
+
 #[no_mangle]
 pub extern "C" fn Servo_LayerStatementRule_GetNameCount(rule: &LayerStatementRule) -> usize {
     rule.names.len()
diff --git a/startupcache/StartupCache.h b/startupcache/StartupCache.h
index 5894a1c9c9ebb..93ddc217cfc56 100644
--- a/startupcache/StartupCache.h
+++ b/startupcache/StartupCache.h
@@ -244,7 +244,7 @@ class StartupCache : public nsIMemoryReporter {
   nsTArray<decltype(mTable)> mOldTables MOZ_GUARDED_BY(mTableLock);
   size_t mAllowedInvalidationsCount;
   nsCOMPtr<nsIFile> mFile;
-  loader::AutoMemMap mCacheData MOZ_GUARDED_BY(mTableLock);
+  mozilla::loader::AutoMemMap mCacheData MOZ_GUARDED_BY(mTableLock);
   Mutex mTableLock;
 
   nsCOMPtr<nsIObserverService> mObserverService;
diff --git a/testing/web-platform/meta/css/css-cascade/__dir__.ini b/testing/web-platform/meta/css/css-cascade/__dir__.ini
index 221167417d0d2..8a09ade22f60f 100644
--- a/testing/web-platform/meta/css/css-cascade/__dir__.ini
+++ b/testing/web-platform/meta/css/css-cascade/__dir__.ini
@@ -1 +1 @@
-prefs: [layout.css.import-supports.enabled:true, layout.css.properties-and-values.enabled:true]
+prefs: [layout.css.import-supports.enabled:true, layout.css.properties-and-values.enabled:true, layout.css.at-scope.enabled:true]
diff --git a/testing/web-platform/meta/css/css-cascade/at-scope-parsing.html.ini b/testing/web-platform/meta/css/css-cascade/at-scope-parsing.html.ini
deleted file mode 100644
index dd3a35e95ff8f..0000000000000
--- a/testing/web-platform/meta/css/css-cascade/at-scope-parsing.html.ini
+++ /dev/null
@@ -1,80 +0,0 @@
-[at-scope-parsing.html]
-  expected:
-    if (os == "android") and fission: [OK, TIMEOUT]
-  [@scope (.a) is valid]
-    expected: FAIL
-
-  [@scope (.a + .b) is valid]
-    expected: FAIL
-
-  [@scope (.a:hover) is valid]
-    expected: FAIL
-
-  [@scope (.a:hover, #b, div) is valid]
-    expected: FAIL
-
-  [@scope (:is(div, span)) is valid]
-    expected: FAIL
-
-  [@scope (.a) to (.b) is valid]
-    expected: FAIL
-
-  [@scope (.a)to (.b) is valid]
-    expected: FAIL
-
-  [@scope (.a) to (.b:hover, #c, div) is valid]
-    expected: FAIL
-
-  [@scope (.c <> .d) is valid]
-    expected: FAIL
-
-  [@scope (.a, .c <> .d) is valid]
-    expected: FAIL
-
-  [@scope (.a <> .b, .c) is valid]
-    expected: FAIL
-
-  [@scope (div::before) is valid]
-    expected: FAIL
-
-  [@scope (div::after) is valid]
-    expected: FAIL
-
-  [@scope (slotted(div)) is valid]
-    expected: FAIL
-
-  [@scope (.a) to (div::before) is valid]
-    expected: FAIL
-
-  [@scope is valid]
-    expected: FAIL
-
-  [@scope (.a) to (&) is valid]
-    expected: FAIL
-
-  [@scope (.a) to (& > &) is valid]
-    expected: FAIL
-
-  [@scope (.a) to (> .b) is valid]
-    expected: FAIL
-
-  [@scope (.a) to (+ .b) is valid]
-    expected: FAIL
-
-  [@scope (.a) to (~ .b) is valid]
-    expected: FAIL
-
-  [@scope to (.a) is valid]
-    expected: FAIL
-
-  [@scope (> &) to (>>) is valid]
-    expected: FAIL
-
-  [@scope () is valid]
-    expected: FAIL
-
-  [@scope to () is valid]
-    expected: FAIL
-
-  [@scope () to () is valid]
-    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/idlharness.html.ini b/testing/web-platform/meta/css/css-cascade/idlharness.html.ini
deleted file mode 100644
index cd50fa8256127..0000000000000
--- a/testing/web-platform/meta/css/css-cascade/idlharness.html.ini
+++ /dev/null
@@ -1,35 +0,0 @@
-[idlharness.html]
-  expected:
-    if (os == "android") and fission: [OK, TIMEOUT]
-  [CSSScopeRule interface: existence and properties of interface object]
-    expected: FAIL
-
-  [CSSScopeRule interface object length]
-    expected: FAIL
-
-  [CSSScopeRule interface object name]
-    expected: FAIL
-
-  [CSSScopeRule interface: existence and properties of interface prototype object]
-    expected: FAIL
-
-  [CSSScopeRule interface: existence and properties of interface prototype object's "constructor" property]
-    expected: FAIL
-
-  [CSSScopeRule interface: existence and properties of interface prototype object's @@unscopables property]
-    expected: FAIL
-
-  [CSSScopeRule interface: attribute start]
-    expected: FAIL
-
-  [CSSScopeRule interface: attribute end]
-    expected: FAIL
-
-  [Stringification of scope]
-    expected: FAIL
-
-  [CSSScopeRule interface: scope must inherit property "start" with the proper type]
-    expected: FAIL
-
-  [CSSScopeRule interface: scope must inherit property "end" with the proper type]
-    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-cssom.html.ini b/testing/web-platform/meta/css/css-cascade/scope-cssom.html.ini
deleted file mode 100644
index 510a64990d1db..0000000000000
--- a/testing/web-platform/meta/css/css-cascade/scope-cssom.html.ini
+++ /dev/null
@@ -1,39 +0,0 @@
-[scope-cssom.html]
-  [CSSScopeRule.cssText, implicit scope]
-    expected: FAIL
-
-  [CSSScopeRule.cssText, root only]
-    expected: FAIL
-
-  [CSSScopeRule.cssText, root and limit]
-    expected: FAIL
-
-  [CSSScopeRule.cssText, limit only]
-    expected: FAIL
-
-  [CSSScopeRule.start, implicit scope]
-    expected: FAIL
-
-  [CSSScopeRule.start, root only]
-    expected: FAIL
-
-  [CSSScopeRule.start, root and limit]
-    expected: FAIL
-
-  [CSSScopeRule.start, limit only]
-    expected: FAIL
-
-  [CSSScopeRule.end, implicit scope]
-    expected: FAIL
-
-  [CSSScopeRule.end, root only]
-    expected: FAIL
-
-  [CSSScopeRule.end, root and limit]
-    expected: FAIL
-
-  [CSSScopeRule.end, limit only]
-    expected: FAIL
-
-  [CSSScopeRule is a CSSGroupingRule]
-    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-deep.html.ini b/testing/web-platform/meta/css/css-cascade/scope-deep.html.ini
deleted file mode 100644
index 3968ff3a8bced..0000000000000
--- a/testing/web-platform/meta/css/css-cascade/scope-deep.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[scope-deep.html]
-  expected:
-    if (os == "android") and fission: [OK, TIMEOUT]
-  [Deep @scope nesting]
-    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini b/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini
index 1768b21bb49e1..44d48f4b05c60 100644
--- a/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini
+++ b/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini
@@ -17,9 +17,6 @@
   [Inner @scope with :scope in from-selector]
     expected: FAIL
 
-  [Multiple scopes from same @scope-rule, only one limited]
-    expected: FAIL
-
   [Nested scopes]
     expected: FAIL
 
@@ -43,3 +40,15 @@
 
   [Scope root with :has()]
     expected: FAIL
+
+  [Scope can not match its own root without :scope]
+    expected: FAIL
+
+  [Multiple scopes from same @scope-rule, both limited]
+    expected: FAIL
+
+  [Nested scopes, reverse]
+    expected: FAIL
+
+  [Scope with no elements]
+    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini b/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini
index dc73a3cb37759..a91abe192e14e 100644
--- a/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini
+++ b/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini
@@ -16,3 +16,9 @@
 
   [Implicit @scope with limit]
     expected: FAIL
+
+  [@scope with effectively empty :is() must not match anything]
+    expected: FAIL
+
+  [Implicit @scope has implicitly added :scope descendant combinator]
+    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-name-defining-rules.html.ini b/testing/web-platform/meta/css/css-cascade/scope-name-defining-rules.html.ini
deleted file mode 100644
index a5ef978d27991..0000000000000
--- a/testing/web-platform/meta/css/css-cascade/scope-name-defining-rules.html.ini
+++ /dev/null
@@ -1,12 +0,0 @@
-[scope-name-defining-rules.html]
-  [@keyframes is unaffected by @scope]
-    expected: FAIL
-
-  [@keyframes is unaffected by non-matching @scope]
-    expected: FAIL
-
-  [@property is unaffected by @scope]
-    expected: FAIL
-
-  [@property is unaffected by non-matching @scope]
-    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini b/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini
index 0d535cd082b22..ff297793265fd 100644
--- a/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini
+++ b/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini
@@ -46,3 +46,6 @@
 
   [Scoped nested group rule]
     expected: FAIL
+
+  [Nesting-selector in <scope-end>]
+    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini b/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini
index 2e16da28b1e9c..33bb292b80960 100644
--- a/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini
+++ b/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini
@@ -6,6 +6,3 @@
 
   [Proximity wins over order of appearance]
     expected: FAIL
-
-  [Specificity wins over proximity]
-    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini b/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini
index bf47d2b4f77bc..088f046448b42 100644
--- a/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini
+++ b/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini
@@ -1,7 +1,4 @@
 [scope-shadow.tentative.html]
-  [@scope can match :host]
-    expected: FAIL
-
   [@scope can match :host(...)]
     expected: FAIL
 
diff --git a/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini b/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini
index beda7272c7c1e..c73271a9e84cd 100644
--- a/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini
+++ b/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini
@@ -1,16 +1,4 @@
 [scope-visited-cssom.html]
-  [:link as scoped selector]
-    expected: FAIL
-
-  [:not(:visited) as scoped selector]
-    expected: FAIL
-
-  [:link as scoping root]
-    expected: FAIL
-
-  [:not(:visited) as scoping root]
-    expected: FAIL
-
   [:link as scoping root, :scope]
     expected: FAIL
 
@@ -22,3 +10,9 @@
 
   [:not(:link) as scoping limit]
     expected: FAIL
+
+  [:visited as scoping root]
+    expected: FAIL
+
+  [:not(:link) as scoping root]
+    expected: FAIL
diff --git a/testing/web-platform/meta/css/css-nesting/conditional-rules.html.ini b/testing/web-platform/meta/css/css-nesting/conditional-rules.html.ini
new file mode 100644
index 0000000000000..1275f8492c5f1
--- /dev/null
+++ b/testing/web-platform/meta/css/css-nesting/conditional-rules.html.ini
@@ -0,0 +1,3 @@
+prefs: [layout.css.at-scope.enabled:true]
+[conditional-rules.html]
+  expected: FAIL
diff --git a/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html
index 88e28fe4ff744..e984c1dcc29e1 100644
--- a/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html
+++ b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html
@@ -75,4 +75,5 @@
   test_invalid('@scope (.a');
   test_invalid('@scope (.a to (.b)');
   test_invalid('@scope ( to (.b)');
+  test_invalid('@scope (.a) from (.c)');
 </script>
-- 
GitLab