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 sensíveis, como pop-ups, tela cheia etc., os navegadores controlam o acesso a essas APIs pela 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" normalmente implica que o usuário está interagendo com a página ou concluiu uma interação desde o carregamento da página. Gesto do usuário é um termo comum, mas enganoso, para a mesma ideia. Por exemplo, um gesto de deslizar ou tocar por 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.

Os principais navegadores atuais mostram um comportamento muito diferente em relação a como a ativação do usuário controla as APIs de 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 com ativação obrigatória. Por exemplo, o Chrome permitia acesso incompleto a APIs com ativação obrigatória usando postMessage() e chamadas setTimeout(). Além disso, a ativação do usuário não era compatível com Promises, XHR, interação com gamepad etc. Alguns desses bugs são conhecidos e antigos.

Na versão 72, o Chrome envia a Ativação do usuário v2, que torna a disponibilidade da ativação do usuário completa para todas as APIs ativadas. Isso resolve as inconsistências mencionadas acima (e algumas outras, como MessageChannels), o que acreditamos que facilitaria o desenvolvimento da Web em relação à ativação do usuário. Além disso, a nova implementação oferece uma implementação de referência para uma 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 de ativação do usuário histórico (se um frame já tiver mostrado uma ativação do usuário) e um bit transitório para o estado atual (se um frame tiver mostrado uma ativação do usuário em cerca de um segundo). O bit fixo nunca é redefinido durante a vida útil do frame depois de ser definido. O bit transitório é definido em cada interação do usuário e é redefinido após um intervalo de expiração (cerca de um segundo) ou por uma chamada para uma API que consome ativação (por exemplo, window.open()).

Diferentes APIs com ativação obrigatória dependem da ativação do usuário de maneiras diferentes. A nova API não muda 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 sendo eficaz se um frame (ou qualquer um dos subframes) já tiver recebido uma ação do usuário, e assim por diante.

O que muda?

  • A Ativação do usuário v2 formaliza a noção de visibilidade da ativação do usuário em limites de frame: uma interação do usuário com um frame específico agora ativa todos os frames que contêm (e somente esses 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 quando tivermos uma maneira de transmitir explicitamente a ativação do usuário para subframes.
  • Quando uma API de ativação restrita é chamada de um frame ativado, mas fora do código do manipulador de eventos, ela vai funcionar desde que o estado de ativação do usuário esteja "ativo" (por exemplo, não tenha expirado nem sido consumido). Antes da ativação do usuário v2, isso falharia incondicionalmente.
  • Várias interações do usuário não utilizadas dentro do intervalo de tempo de expiração são mescladas em uma única ativação correspondente à última interação.

Exemplos de consistência em APIs com ativação obrigatória

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 com ativação consistente.

Chamadas setTimeout() encadeadas

Este exemplo é da nossa demonstração de setTimeout(). Se um gerenciador click tentar abrir um pop-up em um segundo, ele será sucedido, não importa como o código "compõe" o atraso. A Ativação do usuário v2 atende a essa expectativa. Portanto, 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 versão 2 da ativação do usuário, 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

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

// 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. Mesmo a primeira mensagem falha se for "encadeada" a outro frame de origem cruzada, ou seja, se o primeiro destinatário encaminhar a mensagem para outro.

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