[go: nahoru, domu]

Skip to content

Commit

Permalink
[@scope] Support implicit scoping roots
Browse files Browse the repository at this point in the history
This CL makes it possible to specify a @scope rule without any
prelude, scoping the rules implicitly to the parent of the owner node.

This is implemented by storing a StyleSheetContents in each StyleScope,
and treating each owner-parent-node of the StyleSheetContents as a
scoping root.

The following likely doesn't work, and is postponed for future CLs:

 - Constructed stylesheets
 - An owner node whose parent node is a ShadowRoot

w3c/csswg-drafts#7349

Bug: 1379844
Change-Id: Icbfae88636662e9a16dd11f9c7a371c997447f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4161741
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1093015}
  • Loading branch information
andruud authored and Chromium LUCI CQ committed Jan 16, 2023
1 parent 845d307 commit ca5cbba
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 18 deletions.
17 changes: 12 additions & 5 deletions third_party/blink/renderer/core/css/css_scope_rule.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ String CSSScopeRule::PreludeText() const {
const StyleScope& scope =
To<StyleRuleScope>(*group_rule_.Get()).GetStyleScope();

result.Append('(');
result.Append(scope.From().SelectorsText());
result.Append(')');
if (scope.From()) {
result.Append('(');
result.Append(scope.From()->SelectorsText());
result.Append(')');
}

if (scope.To()) {
DCHECK(scope.From());
result.Append(" to (");
result.Append(scope.To()->SelectorsText());
result.Append(')');
Expand All @@ -36,8 +39,12 @@ String CSSScopeRule::PreludeText() const {

String CSSScopeRule::cssText() const {
StringBuilder result;
result.Append("@scope ");
result.Append(PreludeText());
result.Append("@scope");
String prelude = PreludeText();
if (!prelude.empty()) {
result.Append(" ");
result.Append(prelude);
}
AppendCSSTextForItems(result);
return result.ReleaseString();
}
Expand Down
9 changes: 7 additions & 2 deletions third_party/blink/renderer/core/css/rule_feature_set.cc
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,10 @@ void RuleFeatureSet::UpdateFeaturesFromStyleScope(
const StyleScope& style_scope,
InvalidationSetFeatures& descendant_features) {
for (const StyleScope* scope = &style_scope; scope; scope = scope->Parent()) {
for (const CSSSelector* selector = scope->From().First(); selector;
if (!scope->From()) {
continue;
}
for (const CSSSelector* selector = scope->From()->First(); selector;
selector = CSSSelectorList::Next(*selector)) {
InvalidationSetFeatures scope_features;
ExtractInvalidationSetFeaturesFromCompound(
Expand Down Expand Up @@ -1565,7 +1568,9 @@ void RuleFeatureSet::AddFeaturesToInvalidationSetsForStyleScope(
};

for (const StyleScope* scope = &style_scope; scope; scope = scope->Parent()) {
add_features(scope->From(), descendant_features);
if (scope->From()) {
add_features(*scope->From(), descendant_features);
}

if (scope->To()) {
add_features(*scope->To(), descendant_features);
Expand Down
5 changes: 4 additions & 1 deletion third_party/blink/renderer/core/css/selector_checker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2254,14 +2254,17 @@ const SelectorChecker::Activations* SelectorChecker::CalculateActivations(

// Check if we need to add a new activation for this element.
for (const StyleScopeActivation& activation : outer_activations) {
if (MatchesWithScope(element, style_scope.From(), activation.root)) {
if (style_scope.From()
? MatchesWithScope(element, *style_scope.From(), activation.root)
: style_scope.HasImplicitRoot(&element)) {
activations->push_back(StyleScopeActivation{&element, 0, false});
break;
}
// TODO(crbug.com/1280240): Break if we don't depend on :scope.
}

if (style_scope.To()) {
DCHECK(style_scope.From());
for (StyleScopeActivation& activation : *activations) {
DCHECK(!activation.limit);
if (MatchesWithScope(element, *style_scope.To(), activation.root.Get())) {
Expand Down
28 changes: 26 additions & 2 deletions third_party/blink/renderer/core/css/style_scope.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
#include "third_party/blink/renderer/core/css/style_scope.h"
#include "third_party/blink/renderer/core/css/parser/css_selector_parser.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/element.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"

namespace blink {

StyleScope::StyleScope(CSSSelectorList* from, CSSSelectorList* to)
: from_(from), to_(to) {}

StyleScope::StyleScope(StyleSheetContents* contents) : contents_(contents) {}

StyleScope::StyleScope(const StyleScope& other)
: from_(other.from_->Copy()),
to_(other.to_.Get() ? other.to_->Copy() : nullptr) {}
Expand All @@ -22,10 +26,17 @@ StyleScope* StyleScope::CopyWithParent(const StyleScope* parent) const {
return copy;
}

bool StyleScope::HasImplicitRoot(Element* element) const {
if (!contents_) {
return false;
}
return contents_->HasOwnerParentNode(element);
}

unsigned StyleScope::Specificity() const {
if (!specificity_.has_value()) {
specificity_ =
from_->MaximumSpecificity() + (parent_ ? parent_->Specificity() : 0);
specificity_ = (from_ ? from_->MaximumSpecificity() : 0) +
(parent_ ? parent_->Specificity() : 0);
}
return *specificity_;
}
Expand All @@ -37,6 +48,12 @@ StyleScope* StyleScope::Parse(CSSParserTokenRange prelude,
CSSSelectorList* to = nullptr;

prelude.ConsumeWhitespace();

if (prelude.AtEnd()) {
// Implicitly rooted.
return MakeGarbageCollected<StyleScope>(style_sheet);
}

if (prelude.Peek().GetType() != kLeftParenthesisToken) {
return nullptr;
}
Expand Down Expand Up @@ -74,4 +91,11 @@ StyleScope* StyleScope::Parse(CSSParserTokenRange prelude,
return MakeGarbageCollected<StyleScope>(from, to);
}

void StyleScope::Trace(blink::Visitor* visitor) const {
visitor->Trace(contents_);
visitor->Trace(from_);
visitor->Trace(to_);
visitor->Trace(parent_);
}

} // namespace blink
26 changes: 18 additions & 8 deletions third_party/blink/renderer/core/css/style_scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,44 @@

namespace blink {
class StyleSheetContents;
class Element;

class CORE_EXPORT StyleScope final : public GarbageCollected<StyleScope> {
public:
// Construct a StyleScope with explicit roots specified by elements matching
// the `from` selector list. The (optional) `to` parameter selects the the
// limit elements, i.e. the extent of the scope.
StyleScope(CSSSelectorList* from, CSSSelectorList* to);
// Construct a StyleScope with implicit roots at the parent nodes of the
// stylesheet's owner nodes.
explicit StyleScope(StyleSheetContents* contents);
StyleScope(const StyleScope&);
static StyleScope* Parse(CSSParserTokenRange prelude,
const CSSParserContext* context,
StyleSheetContents* style_sheet);

void Trace(blink::Visitor* visitor) const {
visitor->Trace(from_);
visitor->Trace(to_);
visitor->Trace(parent_);
}
void Trace(blink::Visitor*) const;

StyleScope* CopyWithParent(const StyleScope*) const;

const CSSSelectorList& From() const { return *from_; }
const CSSSelectorList* From() const { return from_; }
const CSSSelectorList* To() const { return to_.Get(); } // May be nullptr.
const StyleScope* Parent() const { return parent_.Get(); }

// True if this StyleScope has an implicit root at the specified element.
// This is used to find the roots for prelude-less @scope rules.
bool HasImplicitRoot(Element*) const;

// Specificity of the <scope-start> selector (::From()), plus the
// specificity of the parent scope (if any).
unsigned Specificity() const;

private:
Member<CSSSelectorList> from_;
Member<CSSSelectorList> to_; // May be nullptr.
// If `contents_` is not nullptr, then this is a prelude-less @scope rule
// which is implicitly scoped to the owner node's parent.
Member<StyleSheetContents> contents_;
Member<CSSSelectorList> from_; // May be nullptr.
Member<CSSSelectorList> to_; // May be nullptr.
Member<const StyleScope> parent_;
mutable absl::optional<unsigned> specificity_;
};
Expand Down
10 changes: 10 additions & 0 deletions third_party/blink/renderer/core/css/style_sheet_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,16 @@ Document* StyleSheetContents::AnyOwnerDocument() const {
return RootStyleSheet()->ClientAnyOwnerDocument();
}

bool StyleSheetContents::HasOwnerParentNode(Node* candidate) const {
for (const WeakMember<CSSStyleSheet>& sheet : completed_clients_) {
if (Node* node = sheet->ownerNode();
node && (node->parentNode() == candidate)) {
return true;
}
}
return false;
}

static bool ChildRulesHaveFailedOrCanceledSubresources(
const HeapVector<Member<StyleRuleBase>>& rules) {
for (unsigned i = 0; i < rules.size(); ++i) {
Expand Down
4 changes: 4 additions & 0 deletions third_party/blink/renderer/core/css/style_sheet_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ class CORE_EXPORT StyleSheetContents final
// if there are none.
Document* AnyOwnerDocument() const;

// True if any the StyleSheetContents's owner nodes have a *parent* that is
// equal to `candidate`.
bool HasOwnerParentNode(Node* candidate) const;

const WTF::TextEncoding& Charset() const {
return parser_context_->Charset();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@scope {
:scope { z-index:1; }
.a { z-index:2; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>@scope - implicit scope root (external sheet)</title>
<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div class="a outside"><div>
<div id=root>
<link rel="stylesheet" href="resources/scope.css">
<div class=a></div>
</div>
<div class="a outside"><div>

<script>
test((t) => {
assert_equals(getComputedStyle(root).zIndex, '1');
assert_equals(getComputedStyle(document.querySelector('#root > .a')).zIndex, '2');

let outside = document.querySelectorAll('.outside');
assert_equals(outside.length, 2);
for (let div of outside) {
assert_equals(getComputedStyle(div).zIndex, 'auto');
}
}, '@scope with external stylesheet');
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!DOCTYPE html>
<title>@scope - implicit scope root</title>
<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main id=main></main>

<template id=test_basic>
<div>
<style>
@scope {
.a { z-index:1; }
}
</style>
<div id=inner class=a></div>
</div>
<div id=outer class=a></div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_basic.content.cloneNode(true));

assert_equals(getComputedStyle(inner).zIndex, '1');
assert_equals(getComputedStyle(outer).zIndex, 'auto');
}, '@scope without prelude implicitly scopes to parent of owner node');
</script>

<template id=test_scope_pseudo>
<div>
<div></div>
</div>
<div>
<div id=root>
<style>
@scope {
:scope { z-index:1; }
}
</style>
<div>
<div></div>
</div>
</div>
</div>
<div>
<div></div>
</div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_scope_pseudo.content.cloneNode(true));

assert_equals(getComputedStyle(root).zIndex, '1');

// Only #root should be affected.
for (let div of main.querySelectorAll('div:not(#root)')) {
assert_equals(getComputedStyle(div).zIndex, 'auto');
}
}, ':scope can style implicit root');
</script>

<template id=test_duplicate>
<div>
<style>
@scope {
.a { z-index:1; }
}
</style>
<div id=first class=a></div>
</div>
<div>
<style>
@scope {
.a { z-index:1; }
}
</style>
<div id=second class=a></div>
</div>
<div id=outer class=a></div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_duplicate.content.cloneNode(true));

assert_equals(getComputedStyle(first).zIndex, '1');
assert_equals(getComputedStyle(second).zIndex, '1');
assert_equals(getComputedStyle(outer).zIndex, 'auto');
}, '@scope works with two identical stylesheets');
</script>

0 comments on commit ca5cbba

Please sign in to comment.