Membuat aktivasi pengguna konsisten di seluruh API

Mustaq Ahmed
Joe Medley
Joe Medley

Untuk mencegah skrip berbahaya menyalahgunakan API sensitif seperti pop-up, layar penuh, dll., browser mengontrol akses ke API tersebut melalui aktivasi pengguna. Aktivasi pengguna adalah status sesi penjelajahan sehubungan dengan tindakan pengguna: status "aktif" biasanya menyiratkan bahwa pengguna saat ini berinteraksi dengan halaman, atau telah menyelesaikan interaksi sejak pemuatan halaman. Gestur pengguna adalah istilah yang populer, tetapi menyesatkan untuk ide yang sama. Misalnya, gestur geser atau putar oleh pengguna tidak mengaktifkan halaman, dan karenanya, dari sudut pandang skrip, bukan aktivasi pengguna.

Browser utama saat ini menunjukkan perilaku yang sangat berbeda terkait cara aktivasi pengguna mengontrol API yang dibatasi aktivasi. Di Chrome, implementasinya didasarkan pada model berbasis token yang ternyata terlalu kompleks untuk menentukan perilaku yang konsisten di semua API yang dibatasi aktivasi. Misalnya, Chrome telah mengizinkan akses tidak lengkap ke API yang dibatasi aktivasi melalui postMessage() dan panggilan setTimeout(); dan aktivasi pengguna tidak didukung dengan Promise, XHR, Interaksi gamepad, dll. Perhatikan bahwa beberapa bug ini populer tetapi merupakan bug yang sudah lama ada.

Pada versi 72, Chrome mengirimkan Aktivasi Pengguna v2 yang membuat ketersediaan aktivasi pengguna lengkap untuk semua API yang dibatasi aktivasi. Cara ini mengatasi inkonsistensi yang disebutkan di atas (dan beberapa hal lainnya, seperti MessageChannels), yang kami yakini akan memudahkan pengembangan web terkait aktivasi pengguna. Selain itu, implementasi baru ini menyediakan implementasi referensi untuk spesifikasi baru yang diusulkan yang bertujuan untuk menyatukan semua browser dalam jangka panjang.

Bagaimana cara kerja Aktivasi Pengguna v2?

API baru ini mempertahankan status aktivasi pengguna dua bit pada setiap objek window dalam hierarki frame: bit melekat untuk status aktivasi pengguna historis (jika frame pernah melihat aktivasi pengguna), dan bit sementara untuk status saat ini (jika frame telah melihat aktivasi pengguna dalam waktu sekitar satu detik). Bit yang melekat tidak pernah direset selama masa pakai frame setelah disetel. Bit sementara ditetapkan pada setiap interaksi pengguna, dan direset setelah interval masa berakhir (sekitar satu detik) atau melalui panggilan ke API yang menggunakan aktivasi (misalnya, window.open()).

Perlu diperhatikan bahwa berbagai API yang dibatasi aktivasi bergantung pada aktivasi pengguna dengan cara yang berbeda pula; API baru tidak mengubah perilaku spesifik API ini. Misalnya, hanya satu pop-up yang diizinkan per aktivasi pengguna karena window.open() menggunakan aktivasi pengguna seperti sebelumnya, Navigator.prototype.vibrate() akan terus efektif jika frame (atau salah satu subframe-nya) pernah melihat tindakan pengguna, dan seterusnya.

Apa yang berubah?

  • Aktivasi Pengguna v2 memformalkan gagasan visibilitas aktivasi pengguna di seluruh batas frame: interaksi pengguna dengan frame tertentu kini akan mengaktifkan semua frame yang memuat (dan hanya frame tersebut) terlepas dari asalnya. (Di Chrome 72, kami memiliki solusi sementara untuk memperluas visibilitas ke semua frame origin yang sama. Kami akan menghapus solusi ini setelah menemukan cara untuk meneruskan aktivasi pengguna secara eksplisit ke sub-frame.)
  • Jika API yang dibatasi aktivasi dipanggil dari frame yang diaktifkan, tetapi dari luar kode pengendali peristiwa, API tersebut akan berfungsi selama status aktivasi pengguna "aktif" (misalnya, belum habis masa berlakunya atau belum digunakan). Sebelum Aktivasi Pengguna v2, pengujian ini akan gagal tanpa syarat.
  • Beberapa interaksi pengguna yang tidak digunakan dalam interval waktu masa berlaku akan digabungkan menjadi satu aktivasi yang sesuai dengan interaksi terakhir.

Contoh konsistensi dalam API yang dibatasi aktivasi

Berikut ini dua contoh dengan jendela pop-up (dibuka menggunakan window.open()) yang menunjukkan cara Aktivasi Pengguna v2 membuat perilaku API yang dibatasi aktivasi menjadi konsisten.

setTimeout() panggilan berantai

Contoh ini berasal dari demo setTimeout() kami. Jika pengendali click mencoba membuka pop-up dalam satu detik, pop-up diharapkan akan berhasil, terlepas dari bagaimana kode "mengomposisi" penundaan. Aktivasi Pengguna v2 memenuhi ekspektasi ini, sehingga setiap pengendali peristiwa berikut membuka pop-up pada click (dengan penundaan 100 milidetik):

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

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

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

Tanpa Aktivasi Pengguna v2, pengendali peristiwa kedua akan gagal di semua browser yang kami uji. (Bahkan yang pertama gagal dalam beberapa kasus.)

Panggilan postMessage() lintas-domain

Berikut ini contoh dari demo postMessage() kami. Misalkan pengendali click dalam subframe lintas origin mengirim dua pesan langsung ke frame induk. Frame induk harus dapat membuka pop-up setelah menerima salah satu pesan ini (tetapi tidak keduanya):

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

Tanpa Aktivasi Pengguna v2, frame induk tidak dapat membuka pop-up setelah menerima pesan kedua. Bahkan pesan pertama akan gagal jika "dirantai" ke frame lintas origin lainnya (dengan kata lain, jika penerima pertama meneruskan pesan ke pesan lain).

Kode ini berfungsi dengan Aktivasi Pengguna v2, baik dalam bentuk aslinya maupun dengan rantai.