Обеспечение единообразия активации пользователей в разных API

Mustaq Ahmed
Джо Медли
Joe Medley

Чтобы вредоносные сценарии не злоупотребляли конфиденциальными API, такими как всплывающие окна, полноэкранный режим и т. д., браузеры контролируют доступ к этим API посредством активации пользователя. Активация пользователя — это состояние сеанса просмотра по отношению к действиям пользователя: «активное» состояние обычно подразумевает, что пользователь в данный момент взаимодействует со страницей или завершил взаимодействие с момента загрузки страницы. Пользовательский жест — популярный, но вводящий в заблуждение термин для обозначения одной и той же идеи. Например, жест смахивания или пролистывания пользователем не активирует страницу и, следовательно, с точки зрения сценария не является активацией пользователя.

Сегодня основные браузеры демонстрируют весьма разное поведение в отношении того, как активация пользователя управляет API-интерфейсами, управляемыми активацией . В Chrome реализация была основана на модели на основе токенов, которая оказалась слишком сложной для определения единообразного поведения во всех API, управляемых активацией. Например, Chrome разрешает неполный доступ к API, управляемым активацией, через вызовы postMessage() и setTimeout() ; и активация пользователя не поддерживалась с помощью Promises , XHR , взаимодействия с геймпадом и т. д. Обратите внимание, что некоторые из них являются популярными, но давними ошибками.

В версии 72 Chrome поставляется с функцией User Activation v2, которая обеспечивает полную доступность пользовательской активации для всех API, управляемых активацией. Это устраняет несоответствия, упомянутые выше (и некоторые другие, такие как MessageChannels ), что, по нашему мнению, облегчит веб-разработку, связанную с активацией пользователя. Более того, новая реализация представляет собой эталонную реализацию предлагаемой новой спецификации , целью которой является объединение всех браузеров в долгосрочной перспективе.

Как работает активация пользователя v2?

Новый API поддерживает двухбитовое состояние пользовательской активации для каждого объекта window в иерархии фреймов: липкий бит для исторического состояния пользовательской активации (если кадр когда-либо видел пользовательскую активацию) и временный бит для текущего состояния (если фрейм увидел активацию пользователя примерно через секунду). Бит липкости никогда не сбрасывается в течение жизни кадра после его установки. Временной бит устанавливается при каждом взаимодействии с пользователем и сбрасывается либо по истечении интервала времени (около секунды), либо посредством вызова API, потребляющего активацию (например, window.open() ).

Обратите внимание, что разные API, управляемые активацией, по-разному полагаются на активацию пользователя; новый API не меняет ни одного из этих особенностей поведения API. Например, для каждой активации пользователя разрешено только одно всплывающее окно, поскольку window.open() использует активацию пользователя, как и раньше, Navigator.prototype.vibrate() продолжает действовать, если фрейм (или любой из его подкадров) когда-либо видел действие пользователя. , и так далее.

Что меняется?

  • User Activation v2 формализует понятие видимости активации пользователя за пределами границ фрейма: взаимодействие пользователя с конкретным фреймом теперь активирует все содержащиеся в нем фреймы (и только эти фреймы) независимо от их происхождения. (В Chrome 72 есть временное решение, позволяющее расширить видимость всех кадров одного происхождения. Мы удалим это решение, как только у нас появится способ явно передавать активацию пользователя в подкадры .)
  • Когда API, управляемый активацией, вызывается из активированного кадра, но извне кода обработчика событий, он будет работать до тех пор, пока состояние активации пользователя является «активным» (например, срок его действия не истек и не использован). До активации пользователя v2 это безоговорочно не удавалось.
  • Несколько неиспользованных взаимодействий пользователя в течение интервала времени истечения срока действия объединяются в одну активацию, соответствующую последнему взаимодействию.

Примеры согласованности в API, управляемых активацией

Вот два примера со всплывающими окнами (открытыми с помощью window.open() ), которые показывают, как User Activation v2 обеспечивает единообразие поведения управляемых активацией API.

Связанные вызовы setTimeout()

Этот пример взят из нашей демонстрации setTimeout() . Если обработчик click пытается открыть всплывающее окно в течение секунды, ожидается, что это удастся, независимо от того, как код «компонует» задержку. User Activation v2 соответствует этому ожиданию, поэтому каждый из следующих обработчиков событий открывает всплывающее окно по click (с задержкой 100 мс):

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

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

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

Без User Activation v2 второй обработчик событий не работает во всех протестированных нами браузерах. (Даже первый из них в некоторых случаях терпит неудачу.)

Междоменные вызовы postMessage()

Вот пример из нашей демонстрации postMessage() . Предположим, что обработчик click в подкадре с разными источниками отправляет два сообщения непосредственно в родительский кадр. Родительский фрейм должен иметь возможность открывать всплывающее окно при получении любого из этих сообщений (но не обоих):

// 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);
});

Без активации пользователя версии 2 родительский фрейм не может открыть всплывающее окно после получения второго сообщения. Даже первое сообщение терпит неудачу, если оно «привязано» к другому кадру перекрестного происхождения (другими словами, если первый получатель пересылает сообщение другому).

Это работает с User Activation v2, как в исходном виде, так и с цепочкой.