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 indica que o usuário está interagindo com a página ou concluiu uma interação desde o carregamento dela. Gesto do usuário é um termo popular, mas enganoso para a mesma ideia. Por exemplo, um gesto de deslizar ou tocar rapidamente 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 comportamentos bastante divergentes em relação à forma 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 com controle de ativação por meio de chamadas postMessage() e setTimeout(). Além disso, a ativação do usuário não era compatível com Promises, XHR, interação com o Gamepad etc. Alguns desses são bugs conhecidos, mas de longa data.

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

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

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 sido ativado por um usuário) e um bit transitório para o estado atual (se um frame tiver sido ativado pelo usuário em cerca de um segundo). O bit fixo nunca é redefinido durante o ciclo de vida do frame depois de definido. O bit transit é 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 controladas por ativação dependem da ativação do usuário de maneiras diferentes. A nova API não está mudando 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 antes, Navigator.prototype.vibrate() continua a ser eficaz se um frame (ou qualquer um dos subframes) já tiver visto a ação do usuário e assim por diante.

O que está mudando?

  • A Ativação do usuário v2 formaliza o conceito de visibilidade da ativação do usuário entre os limites do frame: uma interação do usuário com um frame específico vai ativar todos os frames que o 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 da mesma origem. Vamos remover essa solução alternativa quando houver 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 esteja "ativa" (por exemplo, não expirou nem foi 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 são fundas 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 do setTimeout() (link em inglês). Se um gerenciador click tentar abrir um pop-up em um segundo, ele deverá ser bem-sucedido, independentemente de como o código "componha" o atraso. A ativação do usuário v2 atende a essa expectativa, então cada um dos seguintes manipuladores de eventos 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 falhará em todos os navegadores testados. Até mesmo o primeiro falha em alguns casos.

Chamadas postMessage() entre domínios

Veja um exemplo da nossa demonstração de postMessage() (link em inglês). Suponha que um gerenciador click em um subframe de origem cruzada envie duas mensagens diretamente para o frame pai. O frame pai precisa conseguir abrir um pop-up ao receber uma destas 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é mesmo a primeira mensagem falha 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 no formato original quanto com o encadeamento.