Como tornar a ativação do usuário consistente em todas as APIs

Mustaq Ahmed
Joe Medley
Joe Medley

Para evitar que scripts maliciosos abusem de APIs confidenciais, como pop-ups, tela cheia etc., os navegadores controlam o acesso a essas APIs por meio da ativação do usuário. A ativação do usuário é o estado de uma sessão de navegação em relação às ações do usuário. Um estado "ativo" geralmente indica que o usuário está interagindo com a página ou concluiu uma interação desde o carregamento da página. Gesto do usuário é um termo conhecido, mas enganoso, para a mesma ideia. Por exemplo, um gesto de deslizar ou deslizar de um usuário não ativa uma página e, portanto, não é, do ponto de vista do script, uma ativação do usuário.

Atualmente, os principais navegadores apresentam um comportamento bastante divergente em relação ao modo como a ativação do usuário controla as APIs controladas por ativação. No Chrome, a implementação foi baseada em um modelo baseado em token que acabou sendo muito complexo para definir um comportamento consistente em todas as APIs controladas por ativação. Por exemplo, o Chrome tem permitido acesso incompleto a APIs controladas por ativação usando chamadas postMessage() e setTimeout(), e a ativação de usuários não tinha suporte a promessas, XHR, interação com Gamepad etc. Observe que alguns desses são bugs conhecidos, mas antigos.

Na versão 72, o Chrome envia a ativação do usuário v2, que completa a disponibilidade de ativação para todas as APIs controladas por ativação. Isso resolve as inconsistências mencionadas acima e algumas outras, como MessageChannels, o que acreditamos facilitar o desenvolvimento da Web relacionado à ativação do usuário. Além disso, a nova implementação fornece uma referência para uma nova especificação proposta que visa reunir todos os navegadores a longo prazo.

Como a ativação do usuário v2 funciona?

A nova API mantém um estado de ativação do usuário de dois bits em cada objeto window na hierarquia de frames: um bit fixo para o estado histórico de ativação do usuário (se um frame já tiver visto uma ativação do usuário) e um bit temporário para o estado atual (se um frame tiver visto uma ativação do usuário em cerca de um segundo). Essa parte nunca é redefinida durante o ciclo de vida do frame depois que ele é definido. O bit temporário é definido em cada interação do usuário e é redefinido após um intervalo de expiração (cerca de um segundo) ou por meio de uma chamada para uma API que consome ativação (por exemplo, window.open()).

Observe que diferentes APIs controladas por ativação dependem da ativação do usuário de maneiras diferentes. A nova API não vai mudar nenhum desses comportamentos específicos da API. Por exemplo, apenas um pop-up é permitido por ativação do usuário, porque window.open() consome a ativação do usuário como costumava ser, Navigator.prototype.vibrate() continua a ser eficaz se um frame (ou qualquer um dos subframes) já tiver passado por uma ação do usuário e assim por diante.

O que está mudando?

  • A ativação do usuário v2 formaliza a noção de visibilidade da ativação do usuário além dos limites dos frames: uma interação do usuário com um frame específico agora ativa todos os frames contidos (e somente os frames), independentemente da origem. No Chrome 72, temos uma solução temporária para expandir a visibilidade para todos os frames de mesma origem. Vamos remover essa solução alternativa assim que tivermos uma maneira de transmitir explicitamente a ativação do usuário para subframes.
  • Quando uma API controlada por ativação é chamada de um frame ativado, mas de fora de um código do manipulador de eventos, ela funciona desde que o estado de ativação do usuário seja "ativo", por exemplo, não tenha expirado nem consumido. Antes da ativação do usuário v2, ela falhava incondicionalmente.
  • Várias interações do usuário não utilizadas dentro do intervalo de tempo de validade se fundem em uma única ativação correspondente à última interação.

Exemplos de consistência em APIs controladas por ativação

Confira dois exemplos com janelas pop-up (abertas usando window.open()) que mostram como a ativação do usuário v2 torna o comportamento das APIs controladas por ativação consistente.

Chamadas setTimeout() encadeadas

Este exemplo é da nossa demonstração setTimeout(). Se um gerenciador click tentar abrir um pop-up em um segundo, ele será bem-sucedido, independentemente de como o código "compõe" o atraso. A ativação do usuário v2 atende a essa expectativa, então cada um dos manipuladores de eventos a seguir abre um pop-up em um click (com um atraso de 100 ms):

function popupAfter100ms() {
  setTimeout(callWindowOpen, 100);
}

function asyncPopupAfter100ms() {
  setTimeout(popupAfter100ms, 0);
}

someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);

Sem a ativação do usuário v2, o segundo manipulador de eventos falha em todos os navegadores que testamos. Até mesmo o primeiro falha em alguns casos.

Chamadas postMessage() entre domínios

Veja um exemplo da nossa demonstração do postMessage(). Suponha que um gerenciador click em um subframe de origem cruzada envie duas mensagens diretamente para o frame pai. O frame pai precisa abrir um pop-up ao receber uma dessas mensagens (mas não ambas):

// Parent frame code
window.addEventListener('message', e => {
  if (e.data === 'open_popup' && e.origin === child_origin)
    window.open('about:blank');
});

// Child frame code:
someButton.addEventListener('click', () => {
  parent.postMessage('hi_there', parent_origin);
  parent.postMessage('open_popup', parent_origin);
});

Sem a ativação do usuário v2, o frame pai não pode abrir um pop-up ao receber a segunda mensagem. Até a primeira mensagem vai falhar se estiver "encadeada" a outro frame de origem cruzada (ou seja, se o primeiro receptor encaminhar a mensagem para outro).

Isso funciona com a ativação do usuário v2, tanto na forma original quanto com o encadeamento.