mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Auto merge of #18790 - upsuper:scope, r=emilio
Support :scope pseudo-class This fixes [bug 1406817](https://bugzilla.mozilla.org/show_bug.cgi?id=1406817). <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/18790) <!-- Reviewable:end -->
This commit is contained in:
commit
bbb2a3cde9
10 changed files with 80 additions and 18 deletions
|
@ -87,6 +87,7 @@ use net_traits::request::CorsSettings;
|
|||
use ref_filter_map::ref_filter_map;
|
||||
use script_layout_interface::message::ReflowGoal;
|
||||
use script_thread::ScriptThread;
|
||||
use selectors::Element as SelectorsElement;
|
||||
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
|
||||
use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
|
||||
use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
|
||||
|
@ -2191,10 +2192,12 @@ impl ElementMethods for Element {
|
|||
Err(_) => Err(Error::Syntax),
|
||||
Ok(selectors) => {
|
||||
let quirks_mode = document_from_node(self).quirks_mode();
|
||||
let root = DomRoot::from_ref(self);
|
||||
// FIXME(bholley): Consider an nth-index cache here.
|
||||
let mut ctx = MatchingContext::new(MatchingMode::Normal, None, None,
|
||||
quirks_mode);
|
||||
Ok(matches_selector_list(&selectors, &DomRoot::from_ref(self), &mut ctx))
|
||||
ctx.scope_element = Some(root.opaque());
|
||||
Ok(matches_selector_list(&selectors, &root, &mut ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2209,6 +2212,7 @@ impl ElementMethods for Element {
|
|||
match SelectorParser::parse_author_origin_no_namespace(&selectors) {
|
||||
Err(_) => Err(Error::Syntax),
|
||||
Ok(selectors) => {
|
||||
let self_root = DomRoot::from_ref(self);
|
||||
let root = self.upcast::<Node>();
|
||||
for element in root.inclusive_ancestors() {
|
||||
if let Some(element) = DomRoot::downcast::<Element>(element) {
|
||||
|
@ -2216,6 +2220,7 @@ impl ElementMethods for Element {
|
|||
// FIXME(bholley): Consider an nth-index cache here.
|
||||
let mut ctx = MatchingContext::new(MatchingMode::Normal, None, None,
|
||||
quirks_mode);
|
||||
ctx.scope_element = Some(self_root.opaque());
|
||||
if matches_selector_list(&selectors, &element, &mut ctx) {
|
||||
return Ok(Some(element));
|
||||
}
|
||||
|
@ -2500,7 +2505,7 @@ impl VirtualMethods for Element {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ::selectors::Element for DomRoot<Element> {
|
||||
impl<'a> SelectorsElement for DomRoot<Element> {
|
||||
type Impl = SelectorImpl;
|
||||
|
||||
fn opaque(&self) -> ::selectors::OpaqueElement {
|
||||
|
|
|
@ -284,7 +284,7 @@ fn complex_selector_specificity<Impl>(mut iter: slice::Iter<Component<Impl>>)
|
|||
|
||||
Component::FirstChild | Component::LastChild |
|
||||
Component::OnlyChild | Component::Root |
|
||||
Component::Empty |
|
||||
Component::Empty | Component::Scope |
|
||||
Component::NthChild(..) |
|
||||
Component::NthLastChild(..) |
|
||||
Component::NthOfType(..) |
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use attr::CaseSensitivity;
|
||||
use bloom::BloomFilter;
|
||||
use nth_index_cache::NthIndexCache;
|
||||
use tree::OpaqueElement;
|
||||
|
||||
/// What kind of selector matching mode we should use.
|
||||
///
|
||||
|
@ -88,6 +89,19 @@ pub struct MatchingContext<'a> {
|
|||
/// only.)
|
||||
pub relevant_link_found: bool,
|
||||
|
||||
/// The element which is going to match :scope pseudo-class. It can be
|
||||
/// either one :scope element, or the scoping element.
|
||||
///
|
||||
/// Note that, although in theory there can be multiple :scope elements,
|
||||
/// in current specs, at most one is specified, and when there is one,
|
||||
/// scoping element is not relevant anymore, so we use a single field for
|
||||
/// them.
|
||||
///
|
||||
/// When this is None, :scope will match the root element.
|
||||
///
|
||||
/// See https://drafts.csswg.org/selectors-4/#scope-pseudo
|
||||
pub scope_element: Option<OpaqueElement>,
|
||||
|
||||
quirks_mode: QuirksMode,
|
||||
classes_and_ids_case_sensitivity: CaseSensitivity,
|
||||
}
|
||||
|
@ -125,6 +139,7 @@ impl<'a> MatchingContext<'a> {
|
|||
quirks_mode,
|
||||
relevant_link_found: false,
|
||||
classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
|
||||
scope_element: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -724,6 +724,12 @@ fn matches_simple_selector<E, F>(
|
|||
flags_setter(element, HAS_EMPTY_SELECTOR);
|
||||
element.is_empty()
|
||||
}
|
||||
Component::Scope => {
|
||||
match context.shared.scope_element {
|
||||
Some(ref scope_element) => element.opaque() == *scope_element,
|
||||
None => element.is_root(),
|
||||
}
|
||||
}
|
||||
Component::NthChild(a, b) => {
|
||||
matches_generic_nth_child(element, context, a, b, false, false, flags_setter)
|
||||
}
|
||||
|
|
|
@ -673,6 +673,7 @@ pub enum Component<Impl: SelectorImpl> {
|
|||
FirstChild, LastChild, OnlyChild,
|
||||
Root,
|
||||
Empty,
|
||||
Scope,
|
||||
NthChild(i32, i32),
|
||||
NthLastChild(i32, i32),
|
||||
NthOfType(i32, i32),
|
||||
|
@ -969,6 +970,7 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
|
|||
OnlyChild => dest.write_str(":only-child"),
|
||||
Root => dest.write_str(":root"),
|
||||
Empty => dest.write_str(":empty"),
|
||||
Scope => dest.write_str(":scope"),
|
||||
FirstOfType => dest.write_str(":first-of-type"),
|
||||
LastOfType => dest.write_str(":last-of-type"),
|
||||
OnlyOfType => dest.write_str(":only-of-type"),
|
||||
|
@ -1699,6 +1701,7 @@ fn parse_simple_pseudo_class<'i, P, E, Impl>(parser: &P, name: CowRcStr<'i>)
|
|||
"only-child" => Ok(Component::OnlyChild),
|
||||
"root" => Ok(Component::Root),
|
||||
"empty" => Ok(Component::Empty),
|
||||
"scope" => Ok(Component::Scope),
|
||||
"first-of-type" => Ok(Component::FirstOfType),
|
||||
"last-of-type" => Ok(Component::LastOfType),
|
||||
"only-of-type" => Ok(Component::OnlyOfType),
|
||||
|
|
|
@ -1530,6 +1530,7 @@ pub unsafe extern "C" fn Servo_SelectorList_Matches(
|
|||
None,
|
||||
element.owner_document_quirks_mode(),
|
||||
);
|
||||
context.scope_element = Some(element.opaque());
|
||||
|
||||
selectors::matching::matches_selector_list(selectors, &element, &mut context)
|
||||
}
|
||||
|
|
|
@ -152045,6 +152045,18 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"css/selectors4/scope-without-scoping.html": [
|
||||
[
|
||||
"/css/selectors4/scope-without-scoping.html",
|
||||
[
|
||||
[
|
||||
"/css/reference/ref-filled-green-100px-square.xht",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"css/selectors4/selector-required.html": [
|
||||
[
|
||||
"/css/selectors4/selector-required.html",
|
||||
|
@ -538672,6 +538684,10 @@
|
|||
"607553f41a33ce3630752cdf027c9f904833a19d",
|
||||
"reftest"
|
||||
],
|
||||
"css/selectors4/scope-without-scoping.html": [
|
||||
"f70b8d60543c5a28fcf955b1780f15c03d60991a",
|
||||
"reftest"
|
||||
],
|
||||
"css/selectors4/selector-required-ref.html": [
|
||||
"815bc765614b4c2e3d8f8f6303e6bb2ee0989c23",
|
||||
"support"
|
||||
|
@ -570101,7 +570117,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"dom/nodes/Element-closest.html": [
|
||||
"4171fb8b70948ba2617e05b118aaf5d9367e916f",
|
||||
"5abddb81959019267d8b69002ee95b011b2fe34a",
|
||||
"testharness"
|
||||
],
|
||||
"dom/nodes/Element-firstElementChild-entity-xhtml.xhtml": [
|
||||
|
|
|
@ -4,3 +4,6 @@
|
|||
bug: https://github.com/servo/servo/issues/10781
|
||||
expected: FAIL
|
||||
|
||||
[Element.closest with context node 'test4' and selector ':has(> :scope)']
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>Selectors Level 4: :scope without scoping</title>
|
||||
<link rel="author" title="Xidorn Quan" href="mailto:me@upsuper.org">
|
||||
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
|
||||
<link rel="help" href="https://drafts.csswg.org/selectors-4/#scope-pseudo">
|
||||
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
|
||||
<style>
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: red;
|
||||
}
|
||||
:scope > body > div {
|
||||
background: green;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
|
@ -56,11 +56,10 @@
|
|||
do_test(":first-child" , "test12", "test3");
|
||||
do_test(":invalid" , "test11", "test2");
|
||||
|
||||
do_scope_test(":scope" , "test4");
|
||||
do_scope_test("select > :scope" , "test4");
|
||||
do_scope_test("div > :scope" , "test4");
|
||||
do_scope_test(":has(> :scope)" , "test4");
|
||||
|
||||
do_test(":scope" , "test4", "test4");
|
||||
do_test("select > :scope" , "test4", "test4");
|
||||
do_test("div > :scope" , "test4", "");
|
||||
do_test(":has(> :scope)" , "test4", "test3");
|
||||
function do_test(aSelector, aElementId, aTargetId) {
|
||||
test(function() {
|
||||
var el = document.getElementById(aElementId).closest(aSelector);
|
||||
|
@ -71,13 +70,4 @@ function do_test(aSelector, aElementId, aTargetId) {
|
|||
}
|
||||
}, "Element.closest with context node '" + aElementId + "' and selector '" + aSelector + "'");
|
||||
}
|
||||
|
||||
function do_scope_test(aSelector, aElementId) {
|
||||
test(function() {
|
||||
var el = document.getElementById(aElementId);
|
||||
assert_throws("SYNTAX_ERR", function() {
|
||||
el.closest(aSelector);
|
||||
});
|
||||
}, "Element.closest with context node '" + aElementId + "' and selector '" + aSelector + "' should throw");
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue