mirror of
https://github.com/jquery/jquery.git
synced 2024-11-23 02:54:22 +00:00
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 gh-4453 Closes gh-4454 Ref gh-4332 Ref jquery/sizzle#405
This commit is contained in:
parent
7bdf307b51
commit
df6a7f7f0f
@ -4,12 +4,13 @@ define( [
|
||||
"./var/indexOf",
|
||||
"./var/pop",
|
||||
"./var/push",
|
||||
"./selector/support",
|
||||
|
||||
// The following utils are attached directly to the jQuery object.
|
||||
"./selector/contains",
|
||||
"./selector/escapeSelector",
|
||||
"./selector/uniqueSort"
|
||||
], function( jQuery, document, indexOf, pop, push ) {
|
||||
], function( jQuery, document, indexOf, pop, push, support ) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -230,24 +231,30 @@ function find( selector, context, results, seed ) {
|
||||
// Thanks to Andrew Dupont for this technique.
|
||||
if ( nodeType === 1 && rdescend.test( selector ) ) {
|
||||
|
||||
// Capture the context ID, setting it first if necessary
|
||||
if ( ( nid = context.getAttribute( "id" ) ) ) {
|
||||
nid = jQuery.escapeSelector( nid );
|
||||
} else {
|
||||
context.setAttribute( "id", ( nid = expando ) );
|
||||
// Expand context for sibling selectors
|
||||
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
|
||||
context;
|
||||
|
||||
// We can use :scope instead of the ID hack if the browser
|
||||
// supports it & if we're not changing the context.
|
||||
if ( newContext !== context || !support.scope ) {
|
||||
|
||||
// Capture the context ID, setting it first if necessary
|
||||
if ( ( nid = context.getAttribute( "id" ) ) ) {
|
||||
nid = jQuery.escapeSelector( nid );
|
||||
} else {
|
||||
context.setAttribute( "id", ( nid = expando ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix every selector in the list
|
||||
groups = tokenize( selector );
|
||||
i = groups.length;
|
||||
while ( i-- ) {
|
||||
groups[ i ] = "#" + nid + " " + toSelector( groups[ i ] );
|
||||
groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
|
||||
toSelector( groups[ i ] );
|
||||
}
|
||||
newSelector = groups.join( "," );
|
||||
|
||||
// Expand context for sibling selectors
|
||||
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
|
||||
context;
|
||||
}
|
||||
|
||||
try {
|
||||
|
17
src/selector/support.js
Normal file
17
src/selector/support.js
Normal file
@ -0,0 +1,17 @@
|
||||
define( [
|
||||
"../var/document",
|
||||
"../var/support"
|
||||
], function( document, support ) {
|
||||
|
||||
"use strict";
|
||||
|
||||
// Support: IE 9 - 11+, Edge 12 - 18+
|
||||
// IE/Edge don't support the :scope pseudo-class.
|
||||
try {
|
||||
document.querySelectorAll( ":scope" );
|
||||
support.scope = true;
|
||||
} catch ( e ) {}
|
||||
|
||||
return support;
|
||||
|
||||
} );
|
@ -1631,6 +1631,41 @@ QUnit.test( "context", function( assert ) {
|
||||
}
|
||||
} );
|
||||
|
||||
// Support: IE 11+, Edge 12 - 18+
|
||||
// IE/Edge don't support the :scope pseudo-class so they will trigger MutationObservers.
|
||||
// The test is skipped there.
|
||||
QUnit[
|
||||
( QUnit.isIE || /edge\//i.test( navigator.userAgent ) ) ?
|
||||
"skip" :
|
||||
"test"
|
||||
]( "selectors maintaining context don't trigger mutation observers", function( assert ) {
|
||||
assert.expect( 1 );
|
||||
|
||||
var timeout,
|
||||
done = assert.async(),
|
||||
container = jQuery( "<div/>" ),
|
||||
child = jQuery( "<div/>" );
|
||||
|
||||
child.appendTo( container );
|
||||
container.appendTo( "#qunit-fixture" );
|
||||
|
||||
var observer = new MutationObserver( function() {
|
||||
clearTimeout( timeout );
|
||||
observer.disconnect();
|
||||
assert.ok( false, "Mutation observer fired during selection" );
|
||||
done();
|
||||
} );
|
||||
observer.observe( container[ 0 ], { attributes: true } );
|
||||
|
||||
container.find( "div div" );
|
||||
|
||||
timeout = setTimeout( function() {
|
||||
observer.disconnect();
|
||||
assert.ok( true, "Mutation observer didn't fire during selection" );
|
||||
done();
|
||||
} );
|
||||
} );
|
||||
|
||||
QUnit.test( "caching does not introduce bugs", function( assert ) {
|
||||
assert.expect( 3 );
|
||||
|
||||
|
@ -58,12 +58,24 @@ testIframe(
|
||||
var expected,
|
||||
userAgent = window.navigator.userAgent,
|
||||
expectedMap = {
|
||||
edge: {},
|
||||
ie_11: {},
|
||||
chrome: {},
|
||||
safari: {},
|
||||
firefox: {},
|
||||
ios: {}
|
||||
edge: {
|
||||
scope: undefined
|
||||
},
|
||||
ie_11: {
|
||||
scope: undefined
|
||||
},
|
||||
chrome: {
|
||||
scope: true
|
||||
},
|
||||
safari: {
|
||||
scope: true
|
||||
},
|
||||
firefox: {
|
||||
scope: true
|
||||
},
|
||||
ios: {
|
||||
scope: true
|
||||
}
|
||||
};
|
||||
|
||||
if ( /edge\//i.test( userAgent ) ) {
|
||||
@ -95,6 +107,15 @@ testIframe(
|
||||
j++;
|
||||
}
|
||||
|
||||
// Add an assertion per undefined support prop as it may
|
||||
// not even exist on computedSupport but we still want to run
|
||||
// the check.
|
||||
for ( prop in expected ) {
|
||||
if ( expected[ prop ] === undefined ) {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
assert.expect( j );
|
||||
|
||||
for ( i in expected ) {
|
||||
@ -116,6 +137,15 @@ testIframe(
|
||||
i++;
|
||||
}
|
||||
|
||||
// Add an assertion per undefined support prop as it may
|
||||
// not even exist on computedSupport but we still want to run
|
||||
// the check.
|
||||
for ( prop in expected ) {
|
||||
if ( expected[ prop ] === undefined ) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
assert.expect( i );
|
||||
|
||||
// Record all support props and the failing ones and ensure every test
|
||||
@ -123,7 +153,7 @@ testIframe(
|
||||
for ( browserKey in expectedMap ) {
|
||||
for ( supportTestName in expectedMap[ browserKey ] ) {
|
||||
supportProps[ supportTestName ] = true;
|
||||
if ( expectedMap[ browserKey ][ supportTestName ] !== true ) {
|
||||
if ( !expectedMap[ browserKey ][ supportTestName ] ) {
|
||||
failingSupportProps[ supportTestName ] = true;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user