В блоге WebKit есть статья, хорошо объясняющая, как работает пользовательская активация некоторых браузерных API. Делюсь вольным переводом.
Допустим, у вас есть такой код:
let audioCtx = new AudioContext();
await navigator.share({text: 'test'});
window.open('mefody.dev');
Если этот код просто вызвать сразу при загрузке страницы, то он упадёт с ошибками
NotAllowedError. И это ожидаемое поведение — таким образом браузер защищает пользователя от назойливого поведения веб-страниц вроде попапов, внезапного звука, неожиданной оплаты через Apple Pay и так далее.Чтобы он заработал, нужно обернуть код в
addEventListener одного из следующих событий: keydown, mousedown, pointerdown (если pointerType равен mouse), pointerup (если pointerType не равен mouse), touchend. Таким образом браузер будет уверен, что всякое назойливое произошло уже после того, как пользователь начал взаимодействовать со страницей. То есть как будто он сам нажал кнопку «Воспроизвести» и ожидает подобное поведение страницы.Но если вы напишете такой код:
button.addEventListener('click', async () => {
let audioCtx = new AudioContext();
await navigator.share({text: 'test'});
window.open('mefody.dev');
});
он, на удивление, всё равно не будет работать. Аудиоконтекст создастся, окно шаринга покажется, а вот ссылка не откроется. Дело в том, что
window.open мешают две особенности таких обработчиков.Для начала, пользовательская активация бывает временная (transient) и постоянная (sticky). Когда срабатывает обработчик пользовательской активации, в браузере запускается таймер, во время которого можно выполнять методы API, требующие этой самой активации. Это довольно короткий промежуток, буквально пара секунд. В нашем случае share съедает время этого таймера, на
window.open нужно триггерить активацию снова. Это называется временной активацией.Некоторые API не требуют срабатывания до истечения таймера. Им достаточно знания, что активация была когда-то на странице. Такая активация называется постоянной. Для работы с Web Audio, например, достаточно всего одного клика на странице. Объясняется это тем, что если бы пользователю приходилось кликать по странице на каждый чих внутри узлов работы с аудио, это был бы самый ужасный интерфейс.
Но допустим, что пользователь успеет пошарить что-то буквально за доли секунды, до истечения таймера. Сработает ли
window.open? Всё равно нет. Тут вступает в дело другой механизм — потребление активации. Некоторые API, как только получает возможность выполниться, «потребляют» активацию, то есть сбрасывают таймер. Среди таких методов: requestPermission, show для Payment Request и тот самый share. Этот список может пополняться и зависеть от браузера.Так как же проверять, что активация сработала и я могу вызывать свои методы? Для этого есть
navigator.userActivation.isActive для проверки временной активации и navigator.userActivation.hasBeenActive для проверки постоянной активации.https://webkit.org/blog/13862/the-user-activation-api/