Para que serve a pseudoclasse :scope do CSS?

:scope é definido em Seletores de CSS 4 como:

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

Um exemplo de uso é 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 de vermelho e, devido à regra :scope, coloca uma borda ao redor do ul. Isso ocorre porque, no contexto desse <style scoped>, o ul corresponde a :scope. É o contexto local. Se adicionarmos uma regra :scope na <style> externa, ela vai corresponder a todo o documento. Basicamente, equivalente a :root.

Elementos contextuais

Você provavelmente conhece a versão Element de querySelector() e 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. No segundo exemplo, o navegador encontra todos os elementos a e filtra aqueles que não estão no elemento scope. Isso funciona, mas pode levar a um comportamento estranho se você não tomar cuidado. Continue lendo.

Quando o querySelector dá errado

Há um ponto muito importante na especificação de seletores que as pessoas costumam ignorar. Mesmo quando querySelector[All]() é invocado em um elemento, os seletores ainda são avaliados no contexto de todo o documento. Isso significa que coisas inesperadas 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 posso usá-lo e corresponde aos nós. Na segunda, body nem é um descendente do meu elemento, mas "body ul a" ainda corresponde. Ambas são confusas e não são o que você esperaria.

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

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

…digite :scope para resolver essas confusões semânticas.

Como corrigir o querySelector com :scope

O WebKit lançou recentemente suporte para o uso da pseudoclasse :scope em querySelector[All](). Você pode testar isso no Chrome Canary 27.

Você pode usá-lo para restringir seletores a um elemento de contexto. Vamos conferir um exemplo. No exemplo abaixo, :scope é usado para "escopo" do seletor para o subárvore do elemento de escopo. Isso mesmo, eu disse 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á fazem.

Vitória de desempenho?

Ainda não :(

Eu queria saber se o uso de :scope em qS/qSA aumenta a performance. Como um bom engenheiro, fiz um teste. Minha justificativa: menos área de superfície para o navegador fazer a correspondência de seletor significa pesquisas mais rápidas.

No meu experimento, o WebKit leva cerca de 1,5 a 2 vezes mais tempo do que não usar :scope. Droga! Quando o crbug.com/222028 for corrigido, o uso dele teoricamente vai dar um pequeno aumento de desempenho em relação ao não uso.