Skip to content

Commit 466a369

Browse files
committed
Selector: Leverage the :scope pseudo-class where possible
The `:scope` pseudo-class[1] has surprisingly good browser support: Chrome, Firefox & Safari have supported if for a long time; only IE & Edge lack support. This commit leverages this pseudo-class to get rid of the ID hack in most cases. Adding a temporary ID may cause layout thrashing which was reported a few times in [the past. We can't completely eliminate the ID hack in modern browses as sibling selectors require us to change context to the parent and then `:scope` stops applying to what we'd like. But it'd still improve performance in the vast majority of cases. [1] https://developer.mozilla.org/en-US/docs/Web/CSS/:scope Fixes jquerygh-4453 Ref jquerygh-4332 Ref jquery/sizzle#405
1 parent cef4b73 commit 466a369

File tree

3 files changed

+54
-17
lines changed

3 files changed

+54
-17
lines changed

src/selector.js

+19-11
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ define( [
44
"./var/indexOf",
55
"./var/pop",
66
"./var/push",
7+
"./selector/support",
78

89
// The following utils are attached directly to the jQuery object.
910
"./selector/contains",
1011
"./selector/escapeSelector",
1112
"./selector/uniqueSort"
12-
], function( jQuery, document, indexOf, pop, push ) {
13+
], function( jQuery, document, indexOf, pop, push, support ) {
1314

1415
"use strict";
1516

@@ -153,7 +154,7 @@ function selectorError( msg ) {
153154
}
154155

155156
function find( selector, context, results, seed ) {
156-
var m, i, elem, nid, match, groups, newSelector,
157+
var m, i, elem, nid, match, groups, newSelector, canUseScope,
157158
newContext = context && context.ownerDocument,
158159

159160
// nodeType defaults to 9, since context defaults to document
@@ -230,24 +231,31 @@ function find( selector, context, results, seed ) {
230231
// Thanks to Andrew Dupont for this technique.
231232
if ( nodeType === 1 && rdescend.test( selector ) ) {
232233

234+
// Expand context for sibling selectors
235+
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
236+
context;
237+
238+
// We can use :scope instead of the ID hack if the browser
239+
// supports it & if we're not changing the context.
240+
canUseScope = newContext === context && support.cssScope;
241+
233242
// Capture the context ID, setting it first if necessary
234-
if ( ( nid = context.getAttribute( "id" ) ) ) {
235-
nid = jQuery.escapeSelector( nid );
236-
} else {
237-
context.setAttribute( "id", ( nid = expando ) );
243+
if ( !canUseScope ) {
244+
if ( ( nid = context.getAttribute( "id" ) ) ) {
245+
nid = jQuery.escapeSelector( nid );
246+
} else {
247+
context.setAttribute( "id", ( nid = expando ) );
248+
}
238249
}
239250

240251
// Prefix every selector in the list
241252
groups = tokenize( selector );
242253
i = groups.length;
243254
while ( i-- ) {
244-
groups[ i ] = "#" + nid + " " + toSelector( groups[ i ] );
255+
groups[ i ] = ( canUseScope || "#" + nid ) + " " +
256+
toSelector( groups[ i ] );
245257
}
246258
newSelector = groups.join( "," );
247-
248-
// Expand context for sibling selectors
249-
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
250-
context;
251259
}
252260

253261
try {

src/selector/support.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
define( [
2+
"../var/documentElement",
3+
"../var/support"
4+
], function( documentElement, support ) {
5+
6+
"use strict";
7+
8+
// Support: IE 9 - 11+, Edge 12 - 18+
9+
// IE/Edge don't support the :scope pseudo-class.
10+
try {
11+
documentElement.querySelector( ":scope" );
12+
support.cssScope = ":scope";
13+
} catch ( e ) {}
14+
15+
return support;
16+
17+
} );

test/unit/support.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,24 @@ testIframe(
5858
var expected,
5959
userAgent = window.navigator.userAgent,
6060
expectedMap = {
61-
edge: {},
62-
ie_11: {},
63-
chrome: {},
64-
safari: {},
65-
firefox: {},
66-
ios: {}
61+
edge: {
62+
cssScope: undefined
63+
},
64+
ie_11: {
65+
cssScope: undefined
66+
},
67+
chrome: {
68+
cssScope: ":scope"
69+
},
70+
safari: {
71+
cssScope: ":scope"
72+
},
73+
firefox: {
74+
cssScope: ":scope"
75+
},
76+
ios: {
77+
cssScope: ":scope"
78+
}
6779
};
6880

6981
if ( /edge\//i.test( userAgent ) ) {

0 commit comments

Comments
 (0)