зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1886441: Part 6 - Implement scoped styles. r=firefox-style-system-reviewers,emilio
Differential Revision: https://phabricator.services.mozilla.com/D207783
This commit is contained in:
Родитель
536dcf63c5
Коммит
f4188d2589
|
@ -391,12 +391,18 @@ impl SelectorMap<Rule> {
|
|||
stylist,
|
||||
include_starting_style
|
||||
);
|
||||
} else if let Some(scopes) = cascade_data.scope_condition_matches(
|
||||
rule.scope_condition_id,
|
||||
stylist,
|
||||
element,
|
||||
matching_context,
|
||||
) {
|
||||
} else {
|
||||
// First element contains the closest matching scope, but the selector may match
|
||||
// a scope root that is further out (e.g. `.foo > .foo .bar > .baz` for `scope-start`
|
||||
// selector `.foo` and inner selector `.bar .baz`). This requires returning all
|
||||
// potential scopes.
|
||||
let scopes = cascade_data.scope_condition_matches(
|
||||
rule.scope_condition_id,
|
||||
stylist,
|
||||
element,
|
||||
matching_context,
|
||||
);
|
||||
|
||||
// We had to gather scope roots first, since the scope element must change before the rule's
|
||||
// selector is tested. Candidates are sorted in proximity.
|
||||
for candidate in scopes {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//!
|
||||
//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles
|
||||
|
||||
use crate::dom::TElement;
|
||||
use crate::applicable_declarations::ScopeProximity;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::selector_parser::{SelectorImpl, SelectorParser};
|
||||
use crate::shared_lock::{
|
||||
|
@ -16,7 +18,9 @@ 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 selectors::context::MatchingContext;
|
||||
use selectors::matching::{matches_selector, matches_selector_list};
|
||||
use selectors::parser::{ParseRelative, Selector, SelectorList};
|
||||
use selectors::OpaqueElement;
|
||||
use servo_arc::Arc;
|
||||
use std::fmt::{self, Write};
|
||||
|
@ -183,4 +187,120 @@ impl ImplicitScopeRoot {
|
|||
Self::ShadowHost(..) | Self::Constructed => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the scope root element, given the element to be styled.
|
||||
pub fn element(&self, current_host: Option<OpaqueElement>) -> Option<OpaqueElement> {
|
||||
match self {
|
||||
Self::InLightTree(e) |
|
||||
Self::InShadowTree(e) |
|
||||
Self::ShadowHost(e) => Some(*e),
|
||||
Self::Constructed => current_host,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Target of this scope.
|
||||
pub enum ScopeTarget<'a> {
|
||||
/// Target matches an element matching the specified selector list.
|
||||
Selector(&'a SelectorList<SelectorImpl>),
|
||||
/// Target matches only the specified element.
|
||||
Element(OpaqueElement),
|
||||
}
|
||||
|
||||
impl<'a> ScopeTarget<'a> {
|
||||
/// Check if the given element is the scope.
|
||||
pub fn check<E: TElement>(
|
||||
&self,
|
||||
element: E,
|
||||
scope: Option<OpaqueElement>,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
) -> bool {
|
||||
match self {
|
||||
Self::Selector(list) => context.nest_for_scope_condition(scope, |context| {
|
||||
matches_selector_list(list, &element, context)
|
||||
}),
|
||||
Self::Element(e) => element.opaque() == *e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A scope root candidate.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ScopeRootCandidate {
|
||||
/// This candidate's scope root.
|
||||
pub root: OpaqueElement,
|
||||
/// Ancestor hop from the element under consideration to this scope root.
|
||||
pub proximity: ScopeProximity,
|
||||
}
|
||||
|
||||
/// Collect potential scope roots for a given element and its scope target.
|
||||
/// The check may not pass the ceiling, if specified.
|
||||
pub fn collect_scope_roots<E>(
|
||||
element: E,
|
||||
ceiling: Option<OpaqueElement>,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
target: &ScopeTarget,
|
||||
matches_shadow_host: bool,
|
||||
) -> Vec<ScopeRootCandidate>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
let mut result = vec![];
|
||||
let mut parent = Some(element);
|
||||
let mut proximity = 0usize;
|
||||
while let Some(p) = parent {
|
||||
if ceiling == Some(p.opaque()) {
|
||||
break;
|
||||
}
|
||||
if target.check(p, ceiling, context) {
|
||||
result.push(ScopeRootCandidate {
|
||||
root: p.opaque(),
|
||||
proximity: ScopeProximity::new(proximity),
|
||||
});
|
||||
// Note that we can't really break here - we need to consider
|
||||
// ALL scope roots to figure out whch one didn't end.
|
||||
}
|
||||
parent = p.parent_element();
|
||||
proximity += 1;
|
||||
// We we got to the top of the shadow tree - keep going
|
||||
// if we may match the shadow host.
|
||||
if parent.is_none() && matches_shadow_host {
|
||||
parent = p.containing_shadow_host();
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Given the scope-end selector, check if the element is outside of the scope.
|
||||
/// That is, check if any ancestor to the root matches the scope-end selector.
|
||||
pub fn element_is_outside_of_scope<E>(
|
||||
selector: &Selector<E::Impl>,
|
||||
element: E,
|
||||
root: OpaqueElement,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
root_may_be_shadow_host: bool,
|
||||
) -> bool
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
let mut parent = Some(element);
|
||||
context.nest_for_scope_condition(Some(root), |context| {
|
||||
while let Some(p) = parent {
|
||||
if matches_selector(selector, 0, None, &p, context) {
|
||||
return true;
|
||||
}
|
||||
if p.opaque() == root {
|
||||
// Reached the top, not lying outside of scope.
|
||||
break;
|
||||
}
|
||||
parent = p.parent_element();
|
||||
if parent.is_none() && root_may_be_shadow_host {
|
||||
if let Some(host) = p.containing_shadow_host() {
|
||||
// Pretty much an edge case where user specified scope-start and -end of :host
|
||||
return host.opaque() == root;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
|
|
@ -39,7 +39,10 @@ use crate::stylesheets::container_rule::ContainerCondition;
|
|||
use crate::stylesheets::import_rule::ImportLayer;
|
||||
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
|
||||
use crate::stylesheets::layer_rule::{LayerName, LayerOrder};
|
||||
use crate::stylesheets::scope_rule::{ImplicitScopeRoot, ScopeBounds};
|
||||
use crate::stylesheets::scope_rule::{
|
||||
collect_scope_roots, element_is_outside_of_scope, ImplicitScopeRoot, ScopeBounds,
|
||||
ScopeRootCandidate, ScopeTarget,
|
||||
};
|
||||
#[cfg(feature = "gecko")]
|
||||
use crate::stylesheets::{
|
||||
CounterStyleRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule,
|
||||
|
@ -68,7 +71,6 @@ use selectors::parser::{
|
|||
SelectorList,
|
||||
};
|
||||
use selectors::visitor::{SelectorListKind, SelectorVisitor};
|
||||
use selectors::OpaqueElement;
|
||||
use servo_arc::{Arc, ArcBorrow};
|
||||
use smallvec::SmallVec;
|
||||
use std::cmp::Ordering;
|
||||
|
@ -2371,15 +2373,6 @@ impl ContainerConditionReference {
|
|||
}
|
||||
}
|
||||
|
||||
/// A scope root candidate.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ScopeRootCandidate {
|
||||
/// This candidate's scope root.
|
||||
pub root: OpaqueElement,
|
||||
/// Ancestor hop from the element under consideration to this scope root.
|
||||
pub proximity: ScopeProximity,
|
||||
}
|
||||
|
||||
/// The id of a given scope condition, a sequentially-increasing identifier
|
||||
/// for a given style set.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
|
||||
|
@ -2783,16 +2776,110 @@ impl CascadeData {
|
|||
|
||||
pub(crate) fn scope_condition_matches<E>(
|
||||
&self,
|
||||
_id: ScopeConditionId,
|
||||
_stylist: &Stylist,
|
||||
_element: E,
|
||||
_context: &mut MatchingContext<E::Impl>,
|
||||
) -> Option<Vec<ScopeRootCandidate>>
|
||||
id: ScopeConditionId,
|
||||
stylist: &Stylist,
|
||||
element: E,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
) -> Vec<ScopeRootCandidate>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
// TODO(dshin, bug 1886441)
|
||||
None
|
||||
let condition_ref = &self.scope_conditions[id.0 as usize];
|
||||
let bounds = match condition_ref.condition {
|
||||
None => return vec![],
|
||||
Some(ref c) => c,
|
||||
};
|
||||
// Make sure the parent scopes ara evaluated first. This runs a bit counter to normal
|
||||
// selector matching where rightmost selectors match first. However, this avoids having
|
||||
// to traverse through descendants (i.e. Avoids tree traversal vs linear traversal).
|
||||
let outer_scope_roots =
|
||||
self.scope_condition_matches(condition_ref.parent, stylist, element, context);
|
||||
let is_outermost_scope = condition_ref.parent == ScopeConditionId::none();
|
||||
if !is_outermost_scope && outer_scope_roots.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let (root_target, matches_shadow_host) = if let Some(start) = bounds.start.as_ref() {
|
||||
(
|
||||
ScopeTarget::Selector(start),
|
||||
start.slice().iter().any(|s| {
|
||||
!s.matches_featureless_host_selector_or_pseudo_element()
|
||||
.is_empty()
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
let implicit_root = condition_ref
|
||||
.implicit_scope_root
|
||||
.as_ref()
|
||||
.expect("No boundaries, no implicit root?");
|
||||
match implicit_root {
|
||||
StylistImplicitScopeRoot::Normal(r) => {
|
||||
match r.element(context.current_host.clone()) {
|
||||
None => return vec![],
|
||||
Some(root) => (ScopeTarget::Element(root), r.matches_shadow_host()),
|
||||
}
|
||||
},
|
||||
StylistImplicitScopeRoot::Cached(index) => {
|
||||
use crate::dom::TShadowRoot;
|
||||
let shadow_root = if let Some(root) = element.shadow_root() {
|
||||
root
|
||||
} else {
|
||||
element.containing_shadow().expect("Not shadow host and not under shadow tree?")
|
||||
};
|
||||
match shadow_root.implicit_scope_for_sheet(*index) {
|
||||
None => return vec![],
|
||||
Some(root) => {
|
||||
match root.element(context.current_host.clone()) {
|
||||
None => return vec![],
|
||||
Some(r) => (ScopeTarget::Element(r), root.matches_shadow_host()),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let potential_scope_roots = if is_outermost_scope {
|
||||
collect_scope_roots(element, None, context, &root_target, matches_shadow_host)
|
||||
} else {
|
||||
let mut result = vec![];
|
||||
for activation in &outer_scope_roots {
|
||||
let mut this_result = collect_scope_roots(
|
||||
element,
|
||||
Some(activation.root),
|
||||
context,
|
||||
&root_target,
|
||||
matches_shadow_host,
|
||||
);
|
||||
result.append(&mut this_result);
|
||||
}
|
||||
result
|
||||
};
|
||||
|
||||
if potential_scope_roots.is_empty() {
|
||||
return potential_scope_roots;
|
||||
}
|
||||
|
||||
if let Some(end) = bounds.end.as_ref() {
|
||||
let mut result = vec![];
|
||||
// If any scope-end selector matches, we're not in scope.
|
||||
for scope_root in potential_scope_roots {
|
||||
if end.slice().iter().all(|selector| {
|
||||
!element_is_outside_of_scope(
|
||||
selector,
|
||||
element,
|
||||
scope_root.root,
|
||||
context,
|
||||
matches_shadow_host,
|
||||
)
|
||||
}) {
|
||||
result.push(scope_root);
|
||||
}
|
||||
}
|
||||
result
|
||||
} else {
|
||||
potential_scope_roots
|
||||
}
|
||||
}
|
||||
|
||||
fn did_finish_rebuild(&mut self) {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[scope-container.html]
|
||||
[Style rules within @container are scoped]
|
||||
expected: FAIL
|
|
@ -1,57 +0,0 @@
|
|||
[scope-evaluation.html]
|
||||
[Single scope]
|
||||
expected: FAIL
|
||||
|
||||
[Single scope with limit]
|
||||
expected: FAIL
|
||||
|
||||
[Single scope, :scope pseudo in main selector]
|
||||
expected: FAIL
|
||||
|
||||
[Single scope, :scope pseudo in to-selector]
|
||||
expected: FAIL
|
||||
|
||||
[Multiple scopes, :scope pseudo in to-selector]
|
||||
expected: FAIL
|
||||
|
||||
[Inner @scope with :scope in from-selector]
|
||||
expected: FAIL
|
||||
|
||||
[Nested scopes]
|
||||
expected: FAIL
|
||||
|
||||
[Nested scopes, with to-selector]
|
||||
expected: FAIL
|
||||
|
||||
[:scope selecting itself]
|
||||
expected: FAIL
|
||||
|
||||
[The scoping limit is not in scope]
|
||||
expected: FAIL
|
||||
|
||||
[Simulated inclusive scoping limit]
|
||||
expected: FAIL
|
||||
|
||||
[Selecting self with :scope]
|
||||
expected: FAIL
|
||||
|
||||
[Relative selector inside @scope]
|
||||
expected: FAIL
|
||||
|
||||
[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
|
||||
|
||||
[Any scope limit makes the element out of scope]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[scope-implicit-external.html]
|
||||
[@scope with external stylesheet through link element]
|
||||
expected: FAIL
|
||||
|
||||
[@scope with external stylesheet through @import]
|
||||
expected: FAIL
|
|
@ -1,27 +1,3 @@
|
|||
[scope-implicit.html]
|
||||
[@scope without prelude implicitly scopes to parent of owner node]
|
||||
expected: FAIL
|
||||
|
||||
[:scope can style implicit root]
|
||||
expected: FAIL
|
||||
|
||||
[@scope works with two identical stylesheets]
|
||||
expected: FAIL
|
||||
|
||||
[Implicit @scope with inner relative selector]
|
||||
expected: FAIL
|
||||
|
||||
[Implicit @scope with inner nesting selector]
|
||||
expected: FAIL
|
||||
|
||||
[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
|
||||
|
||||
[Proximity calculation of multiple implicit @scope]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[scope-layer.html]
|
||||
[Style rules within @layer are scoped]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[scope-media.html]
|
||||
[Style rules within @media are scoped]
|
||||
expected: FAIL
|
|
@ -1,51 +0,0 @@
|
|||
[scope-nesting.html]
|
||||
[Implicit :scope in <scope-end>]
|
||||
expected: FAIL
|
||||
|
||||
[Relative selectors in <scope-end>]
|
||||
expected: FAIL
|
||||
|
||||
[Nesting-selector in the scope's <stylesheet>]
|
||||
expected: FAIL
|
||||
|
||||
[Nesting-selector within :scope rule]
|
||||
expected: FAIL
|
||||
|
||||
[Nesting-selector within :scope rule (double nested)]
|
||||
expected: FAIL
|
||||
|
||||
[@scope nested within style rule]
|
||||
expected: FAIL
|
||||
|
||||
[Parent pseudo class within scope-start]
|
||||
expected: FAIL
|
||||
|
||||
[Parent pseudo class within scope-end]
|
||||
expected: FAIL
|
||||
|
||||
[Parent pseudo class within body of nested @scope]
|
||||
expected: FAIL
|
||||
|
||||
[Implicit rule within nested @scope ]
|
||||
expected: FAIL
|
||||
|
||||
[Implicit rule within nested @scope (proximity)]
|
||||
expected: FAIL
|
||||
|
||||
[Nested :scope inside an :is]
|
||||
expected: FAIL
|
||||
|
||||
[:scope within nested and scoped rule]
|
||||
expected: FAIL
|
||||
|
||||
[:scope within nested and scoped rule (implied &)]
|
||||
expected: FAIL
|
||||
|
||||
[:scope within nested and scoped rule (relative)]
|
||||
expected: FAIL
|
||||
|
||||
[Scoped nested group rule]
|
||||
expected: FAIL
|
||||
|
||||
[Nesting-selector in <scope-end>]
|
||||
expected: FAIL
|
|
@ -1,8 +1,3 @@
|
|||
[scope-proximity.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[Alternating light/dark]
|
||||
expected: FAIL
|
||||
|
||||
[Proximity wins over order of appearance]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[scope-shadow-sharing.html]
|
||||
expected: FAIL
|
|
@ -1,15 +1,3 @@
|
|||
[scope-shadow.tentative.html]
|
||||
[@scope can match :host(...)]
|
||||
expected: FAIL
|
||||
|
||||
[:scope matches host via the scoping root]
|
||||
expected: FAIL
|
||||
|
||||
[:scope within :is() matches host via the scoping root]
|
||||
expected: FAIL
|
||||
|
||||
[Implicit @scope as direct child of shadow root]
|
||||
expected: FAIL
|
||||
|
||||
[Implicit @scope in construted stylesheet]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
[scope-specificity.html]
|
||||
[@scope (#main) { .b { } } and .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) to (.b) { .a { } } and .a]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main, .foo, .bar) { #a { } } and #a]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { div.b { } } and div.b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { :scope .b { } } and .a .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { & .b { } } and #main .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { div .b { } } and div .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { @scope (.a) { .b { } } } and .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { :scope .b { } } and :scope .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope { & .b { } } and :scope .b]
|
||||
expected: FAIL
|
||||
|
||||
[@scope (#main) { > .a { } } and :where(#main) > .a]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[scope-starting-style.html]
|
||||
[Style rules within @starting-style are scoped]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[scope-supports.html]
|
||||
[Style rules within @supports are scoped]
|
||||
expected: FAIL
|
|
@ -1,18 +0,0 @@
|
|||
[scope-visited-cssom.html]
|
||||
[:link as scoping root, :scope]
|
||||
expected: FAIL
|
||||
|
||||
[:not(:visited) as scoping root, :scope]
|
||||
expected: FAIL
|
||||
|
||||
[:visited as scoping limit]
|
||||
expected: FAIL
|
||||
|
||||
[:not(:link) as scoping limit]
|
||||
expected: FAIL
|
||||
|
||||
[:visited as scoping root]
|
||||
expected: FAIL
|
||||
|
||||
[:not(:link) as scoping root]
|
||||
expected: FAIL
|
|
@ -554,7 +554,7 @@ test_scope(document.currentScript, () => {
|
|||
<div class=a>
|
||||
<span id="in"></span>
|
||||
<div class=b>
|
||||
<span id="out"</span>
|
||||
<span id="out"></span>
|
||||
<div class=c></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -121,3 +121,21 @@ test_scope(document.currentScript, () => {
|
|||
assert_equals(getComputedStyle(item).borderColor, 'rgb(0, 128, 0)');
|
||||
}, 'Specificity wins over proximity');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<style>
|
||||
@scope (.foo) {
|
||||
.bar span[id] { border-color:green; }
|
||||
}
|
||||
</style>
|
||||
<div class=foo>
|
||||
<div class="foo bar">
|
||||
<span id=item></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
test_scope(document.currentScript, () => {
|
||||
assert_equals(getComputedStyle(item).borderColor, 'rgb(0, 128, 0)');
|
||||
}, 'Identical root with further proximity is not ignored');
|
||||
</script>
|
||||
|
|
Загрузка…
Ссылка в новой задаче