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. Pengaktifan 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 halaman dimuat. Gestur pengguna adalah istilah populer, tetapi menyesatkan untuk ide yang sama. Misalnya, gestur geser atau ayunkan oleh pengguna tidak mengaktifkan halaman, sehingga, dari sudut pandang skrip, bukan merupakan aktivasi pengguna.

Browser utama saat ini menunjukkan perilaku yang sangat berbeda terkait cara aktivasi pengguna mengontrol API yang dibatasi aktivasi. Di Chrome, penerapannya 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 yang tidak lengkap ke API yang dibatasi aktivasi melalui panggilan postMessage() dan setTimeout(); dan aktivasi pengguna tidak didukung dengan Promises, XHR, interaksi Gamepad, dll. Perhatikan bahwa beberapa dari bug ini adalah bug populer yang sudah lama ada.

Dalam versi 72, Chrome meluncurkan User Activation v2 yang membuat ketersediaan aktivasi pengguna lengkap untuk semua API yang dibatasi aktivasi. Hal ini akan menyelesaikan inkonsistensi yang disebutkan di atas (dan beberapa lagi, seperti MessageChannels), yang kami yakini akan memudahkan pengembangan web seputar 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 mempertahankan status aktivasi pengguna dua bit di 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). Sticky bit tidak pernah direset selama masa aktif frame setelah ditetapkan. Bit sementara ditetapkan pada setiap interaksi pengguna, dan direset setelah interval masa berlaku (sekitar satu detik) atau melalui panggilan ke API yang menggunakan aktivasi (misalnya, window.open()).

Perhatikan bahwa API yang dikontrol aktivasi yang berbeda mengandalkan aktivasi pengguna dengan cara yang berbeda; API baru tidak mengubah perilaku khusus API ini. Misalnya, hanya satu pop-up yang diizinkan per aktivasi pengguna karena window.open() menggunakan aktivasi pengguna seperti biasa, Navigator.prototype.vibrate() akan terus efektif jika frame (atau 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 berisi (dan hanya frame tersebut) terlepas dari asalnya. (Di Chrome 72, kami memiliki solusi sementara untuk memperluas visibilitas ke semua frame dengan origin yang sama. Kami akan menghapus solusi ini setelah memiliki cara untuk meneruskan aktivasi pengguna secara eksplisit ke sub-frame.)
  • Saat API yang dibatasi aktivasi dipanggil dari frame yang diaktifkan, tetapi dari di 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, tindakan ini akan gagal tanpa syarat.
  • Beberapa interaksi pengguna yang tidak digunakan dalam interval waktu habis masa berlaku digabungkan menjadi satu aktivasi yang sesuai dengan interaksi terakhir.

Contoh konsistensi dalam API yang diaktifkan

Berikut adalah dua contoh dengan jendela pop-up (dibuka menggunakan window.open()) yang menunjukkan bagaimana User Activation v2 membuat perilaku API yang dibatasi aktivasi konsisten.

Panggilan setTimeout() berantai

Contoh ini berasal dari demo setTimeout() kami. Jika pengendali click mencoba membuka pop-up dalam hitungan detik, pengendali tersebut diharapkan berhasil, terlepas dari cara kode "membuat" penundaan. Aktivasi Pengguna v2 memenuhi ekspektasi ini, sehingga setiap pengendali peristiwa berikut akan membuka pop-up di click (dengan penundaan 100 md):

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 adalah contoh dari demo postMessage() kami. Misalkan pengendali click di 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 "dikaitkan" ke frame lintas origin lain (dengan kata lain, jika penerima pertama meneruskan pesan ke penerima lain).

Hal ini berfungsi dengan Aktivasi Pengguna v2, baik dalam bentuk asli maupun dengan rantai.