Para que serve a pseudoclasse :scope do CSS?

:scope é definido nos seletores de CSS 4 como:

Uma pseudoclasse que representa qualquer elemento que esteja no conjunto de elementos de referência contextual. Esse é um conjunto (possivelmente vazio) de elementos especificados explicitamente, como o especificado por querySelector(), ou o elemento pai de um elemento <style scoped>, usado para "definir o escopo" de um seletor para que ele corresponda apenas a uma subárvore.

Um exemplo de uso disso está em um <style scoped> (mais informações):

<style>
    li {
    color: blue;
    }
</style>

<ul>
    <style scoped>
    li {
        color: red;
    }
    :scope {
        border: 1px solid red;
    }
    </style>
    <li>abc</li>
    <li>def</li>
    <li>efg</li>
</ul>

<ul>
    <li>hij</li>
    <li>klm</li>
    <li>nop</li>
</ul>

Isso colore os elementos li no primeiro ul vermelho e, devido à regra :scope, coloca uma borda em torno do ul. Isso ocorre porque, no contexto dessa <style scoped>, a ul corresponde à :scope. É o contexto local. Se adicionássemos uma regra :scope no <style> externo, ela corresponderia a todo o documento. Basicamente, equivalente a :root.

Elementos contextuais

Você provavelmente já conhece a versão Element do querySelector() e do querySelectorAll(). Em vez de consultar o documento inteiro, você pode restringir o conjunto de resultados a um elemento contextual:

<ul>
    <li id="scope"><a>abc</a></li>
    <li>def</li>
    <li><a>efg</a></li>
</ul>
<script>
    document.querySelectorAll('ul a').length; // 2

    var scope = document.querySelector('#scope');
    scope.querySelectorAll('a').length; // 1
</script>

Quando eles são chamados, o navegador retorna um NodeList filtrado para incluir apenas o conjunto de nós que a.) correspondem ao seletor e b.) que também são descendentes do elemento de contexto. Portanto, no segundo exemplo, o navegador encontra todos os elementos a e, em seguida, filtra aqueles que não estão no elemento scope. Isso funciona, mas pode causar um comportamento bizarro se você não tomar cuidado. Continue lendo.

Quando o querySeletor dá errado

Há um ponto muito importante na especificação dos seletores que as pessoas geralmente ignoram. Mesmo quando querySelector[All]() é invocado em um elemento, os seletores ainda avaliam no contexto de todo o documento. Isso significa que imprevistos podem acontecer:

    scope.querySelectorAll('ul a').length); // 1
    scope.querySelectorAll('body ul a').length); // 1

É alguma piada? No primeiro exemplo, ul é meu elemento, mas ainda consigo usá-lo e corresponde aos nós. Na segunda, body não é nem um descendente do meu elemento, mas "body ul a" ainda corresponde. As duas coisas são confusas e não são o que você esperaria.

Vale a pena fazer uma comparação com o jQuery aqui, que usa a abordagem certa e faz o que você espera:

    $(scope).find('ul a').length // 0
    $(scope).find('body ul a').length // 0

...insira :scope para resolver esses truques semânticos.

Correção de querySeletor com :scope

O WebKit lançou recentemente compatibilidade com o uso da pseudoclasse :scope em querySelector[All](). É possível testá-la no Chrome Canary 27.

É possível usar essa opção para restringir seletores a um elemento de contexto. Vamos ver um exemplo. A seguir, :scope é usado para definir o escopo do seletor como a subárvore do elemento do escopo. Isso mesmo, eu disse o escopo três vezes!

    scope.querySelectorAll(':scope ul a').length); // 0
    scope.querySelectorAll(':scope body ul a').length); // 0
    scope.querySelectorAll(':scope a').length); // 1

O uso de :scope torna a semântica dos métodos querySelector() um pouco mais previsível e alinhada com o que outros, como o jQuery, já estão fazendo.

Melhor desempenho?

Ainda não :(

Gostaria de saber se o uso do :scope no qS/qSA melhora o desempenho. Então, como um bom engenheiro, fiz um teste. Minha lógica: menos área de superfície para o navegador fazer a correspondência de seletor significa pesquisas mais rápidas.

Em meu experimento, o WebKit leva atualmente de 1,5 a 2 vezes mais tempo do que não usar :scope. Droga! Quando o site crbug.com/222028 for corrigido, teoricamente, o uso dele deve resultar em um pequeno aumento no desempenho do que quando o usuário não o faz.