Skip to content

Commit

Permalink
[@scope] Scope to host element if there's no owner-parent element
Browse files Browse the repository at this point in the history
Implicit scopes, i.e. scopes without a <scope-start> selector,
should scope to the parent element of the owner element:

  <div id=foo>
    <style>
      @scope { ... } /* Scoped to #foo */
    </style>
  </div>

However, there are two ShadowDOM-related cases where we don't have
an owner-parent element:

 1. When the <style> element appears directly below a shadow root.
 2. When the stylesheet is constructed/adopted. In this case we don't
    even have an owner element.

Currently, we handle this by never activating the scope.
This CL fixes this issue by instead scoping implicit @scope rules
to the shadow *host* instead of the *root*. This requires a spec
change [1], therefore the tests are marked as tentative.

Fixed: 1379844

[1] w3c/csswg-drafts#9178

Change-Id: I9146ffb3c6cb5e2f5697dc3d3e62cda9396709b6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4778499
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1184653}
  • Loading branch information
andruud authored and Chromium LUCI CQ committed Aug 17, 2023
1 parent f55ae3a commit c19b3a5
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 22 deletions.
4 changes: 4 additions & 0 deletions third_party/blink/renderer/core/css/css_style_sheet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ void CSSStyleSheet::RemovedAdoptedFromTreeScope(TreeScope& tree_scope) {
adopted_tree_scopes_.erase(&tree_scope);
}

bool CSSStyleSheet::IsAdoptedByTreeScope(TreeScope& tree_scope) {
return adopted_tree_scopes_.Contains(&tree_scope);
}

bool CSSStyleSheet::HasViewportDependentMediaQueries() const {
return media_query_result_flags_.is_viewport_dependent;
}
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/renderer/core/css/css_style_sheet.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class CORE_EXPORT CSSStyleSheet final : public StyleSheet,

void AddedAdoptedToTreeScope(TreeScope& tree_scope);
void RemovedAdoptedFromTreeScope(TreeScope& tree_scope);
bool IsAdoptedByTreeScope(TreeScope& tree_scope);

// Associated document for constructed stylesheet. Always non-null for
// constructed stylesheets, always null otherwise.
Expand Down
2 changes: 1 addition & 1 deletion third_party/blink/renderer/core/css/style_scope.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ bool StyleScope::HasImplicitRoot(Element* element) const {
if (!contents_) {
return false;
}
return contents_->HasOwnerParentNode(element);
return contents_->HasOwnerParentElementOrAdoptiveHost(element);
}

StyleScope* StyleScope::Parse(CSSParserTokenRange prelude,
Expand Down
14 changes: 12 additions & 2 deletions third_party/blink/renderer/core/css/style_sheet_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "third_party/blink/renderer/core/css/style_rule_namespace.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
Expand Down Expand Up @@ -595,10 +596,19 @@ Document* StyleSheetContents::AnyOwnerDocument() const {
return RootStyleSheet()->ClientAnyOwnerDocument();
}

bool StyleSheetContents::HasOwnerParentNode(Node* candidate) const {
bool StyleSheetContents::HasOwnerParentElementOrAdoptiveHost(
Element* candidate) const {
for (const WeakMember<CSSStyleSheet>& sheet : completed_clients_) {
// Handles the normal case of e.g. <div><style>@scope{}</style></div>,
// and (due to ParentOrShadowHostElement) also handles the case where
// the <style> element appears directly below the shadow root.
if (Node* node = sheet->ownerNode();
node && (node->parentNode() == candidate)) {
node && (node->ParentOrShadowHostElement() == candidate)) {
return true;
}
// Handles constructed/adopted stylesheets.
if (IsShadowHost(candidate) &&
sheet->IsAdoptedByTreeScope(*candidate->GetShadowRoot())) {
return true;
}
}
Expand Down
14 changes: 11 additions & 3 deletions third_party/blink/renderer/core/css/style_sheet_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,17 @@ 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;
// True if either:
//
// - The parent element of the stylesheet's owner node is equal
// to `candidate`, or if no such element exists:
// - The stylesheet is adopted by a shadow root attached to
// a host equal to `candidate`.
//
// Note that a single StyleSheetContents can have multiple CSSStyleSheets
// associated with it, and this function returns true if the
// requirements above are met for *any* CSSStyleSheet.
bool HasOwnerParentElementOrAdoptiveHost(Element* candidate) const;

const WTF::TextEncoding& Charset() const {
return parser_context_->Charset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@
</template>
</div>
<script>
setup(() => {
polyfill_declarative_shadow_dom(document);
});

test(() => {
polyfill_declarative_shadow_dom(host_plain);
let a = host_plain.shadowRoot.querySelector('.a');
assert_equals(getComputedStyle(a).zIndex, '1');
}, '@scope can match :host');
Expand All @@ -50,11 +47,8 @@
</template>
</div>
<script>
setup(() => {
polyfill_declarative_shadow_dom(document);
});

test(() => {
polyfill_declarative_shadow_dom(host_functional);
let a = host_functional.shadowRoot.querySelector('.a');
assert_equals(getComputedStyle(a).zIndex, '1');
}, '@scope can match :host(...)');
Expand All @@ -74,11 +68,8 @@
</template>
</div>
<script>
setup(() => {
polyfill_declarative_shadow_dom(document);
});

test(() => {
polyfill_declarative_shadow_dom(host_scope_subject);
assert_equals(getComputedStyle(host_scope_subject).zIndex, '1');
}, ':scope matches host via the scoping root');
</script>
Expand Down Expand Up @@ -106,11 +97,8 @@
</div>
</div>
<script>
setup(() => {
polyfill_declarative_shadow_dom(document);
});

test(() => {
polyfill_declarative_shadow_dom(host_scope_subject_is);
let host = host_scope_subject_is.querySelector('.host');
assert_equals(getComputedStyle(host).zIndex, '1');
let a = host.shadowRoot.querySelector('.a');
Expand All @@ -120,3 +108,63 @@
assert_equals(getComputedStyle(a_outside).zIndex, 'auto');
}, ':scope within :is() matches host via the scoping root');
</script>

<!-- Tentative. https://github.com/w3c/csswg-drafts/issues/9178 -->
<div id=implicit_scope_shadow_parent>
<div class=host>
<template shadowrootmode=open>
<style>
@scope {
/* Matches host */
:scope {
z-index: 1;
}
:scope > .a {
z-index: 2;
}
}
</style>
<div class=a>
</div>
</template>
</div>
</div>
<script>
test(() => {
polyfill_declarative_shadow_dom(implicit_scope_shadow_parent);
let host = implicit_scope_shadow_parent.querySelector('.host');
let a = host.shadowRoot.querySelector('.a');
assert_equals(getComputedStyle(host).zIndex, '1');
assert_equals(getComputedStyle(a).zIndex, '2');
}, 'Implicit @scope as direct child of shadow root');
</script>

<!-- Tentative. https://github.com/w3c/csswg-drafts/issues/9178 -->
<div id=implicit_scope_constructed>
<div class=host>
<template shadowrootmode=open>
<div class=a>
</div>
</template>
</div>
</div>
<script>
test(() => {
polyfill_declarative_shadow_dom(implicit_scope_constructed);
let host = implicit_scope_constructed.querySelector('.host');
let sheet = new CSSStyleSheet();
sheet.replaceSync(`
@scope {
:scope {
z-index: 1;
}
:scope .a {
z-index: 2;
}
`);
host.shadowRoot.adoptedStyleSheets = [sheet];
let a = host.shadowRoot.querySelector('.a');
assert_equals(getComputedStyle(host).zIndex, '1');
assert_equals(getComputedStyle(a).zIndex, '2');
}, 'Implicit @scope in construted stylesheet');
</script>

0 comments on commit c19b3a5

Please sign in to comment.