Kullanıcı etkinleştirmesini API'ler arasında tutarlı hale getirme

Mustaq Ahmed
Joe Medley
Joe Medley

Kötü amaçlı komut dosyalarının pop-up'lar, tam ekran vb. gibi hassas API'leri kötüye kullanmasını önlemek için tarayıcılar, bu API'lere erişimi kullanıcı etkinleştirmesi aracılığıyla kontrol eder. Kullanıcı etkinleştirme, kullanıcı işlemlerine göre bir tarama oturumunun durumudur: "Etkin" durum genellikle kullanıcının şu anda sayfayla etkileşimde olduğunu veya sayfa yüklendikten sonra bir etkileşim tamamladığını gösterir. Kullanıcı hareketi, aynı fikir için kullanılan popüler ancak yanıltıcı bir terimdir. Örneğin, bir kullanıcının yaptığı kaydırma veya parmak ucuyla dokunma hareketi bir sayfayı etkinleştirmez ve bu nedenle komut dosyası açısından kullanıcı etkinleştirmesi değildir.

Günümüzde büyük tarayıcılar, kullanıcı etkinleştirmenin etkinleştirme denetimine sahip API'leri nasıl kontrol ettiği konusunda birbirinden çok farklı davranışlar gösteriyor. Chrome'da, uygulama jeton tabanlı bir modele dayanıyordu. Bu modelin, etkinleştirme kapılı tüm API'lerde tutarlı bir davranış tanımlamak için çok karmaşık olduğu ortaya çıktı. Örneğin, Chrome postMessage() ve setTimeout() çağrıları aracılığıyla etkinleştirme denetimine tabi API'lere eksik erişime izin veriyordu ve kullanıcı etkinleştirme, Promises, XHR, Gamepad etkileşimi vb. ile desteklenmiyordu. Bunların bazılarının popüler ancak uzun süredir var olan hatalar olduğunu unutmayın.

Chrome, 72 sürümünde kullanıcı etkinleştirme özelliğinin 2. sürümünü kullanıma sundu. Bu sürüm, etkinleştirme denetimine tabi tüm API'ler için kullanıcı etkinleştirme özelliğinin kullanılabilirliğini tamamlar. Bu sayede, yukarıda bahsedilen tutarsızlıklar (ve MessageChannels gibi birkaçı daha) giderilir. Bu da kullanıcı etkinleştirmeyle ilgili web geliştirme sürecini kolaylaştıracaktır. Ayrıca yeni uygulama, uzun vadede tüm tarayıcıları bir araya getirmeyi amaçlayan önerilen yeni bir spesifikasyon için referans bir uygulama sağlar.

Kullanıcı Etkinleştirme v2 nasıl çalışır?

Yeni API, çerçeve hiyerarşisindeki her window nesnesinde iki bitlik bir kullanıcı etkinleştirme durumu tutar: Geçmiş kullanıcı etkinleştirme durumu için yapışkan bit (bir çerçeve daha önce kullanıcı etkinleştirme gördüyse) ve mevcut durum için geçici bit (bir çerçeve yaklaşık bir saniye içinde kullanıcı etkinleştirme gördüyse). Yapışkan bit, ayarlandıktan sonra çerçevenin kullanım süresi boyunca hiçbir zaman sıfırlanmaz. Geçici bit, her kullanıcı etkileşiminde ayarlanır ve bir süre dolduktan sonra (yaklaşık bir saniye) veya etkinleştirmeyi kullanan bir API'ye (ör. window.open()) yapılan bir çağrıyla sıfırlanır.

Etkinleştirme denetimine sahip farklı API'lerin kullanıcı etkinleştirmeyi farklı şekillerde kullandığını unutmayın. Yeni API, API'ye özgü bu davranışlardan hiçbirini değiştirmez. Örneğin, window.open() kullanıcı etkinleştirmesini eskisi gibi kullandığından kullanıcı etkinleştirme başına yalnızca bir pop-up'a izin verilir, Navigator.prototype.vibrate() bir çerçeve (veya alt çerçevelerinden herhangi biri) kullanıcı işlemi gördüyse etkili olmaya devam eder ve benzeri.

Neler değişiyor?

  • Kullanıcı Etkinleştirme v2, kare sınırları genelinde kullanıcı etkinleştirme görünürlüğü kavramını resmileştirir: Belirli bir kareyle kullanıcı etkileşimi artık kaynaklarından bağımsız olarak tüm kapsayıcı kareleri (ve yalnızca bu kareleri) etkinleştirir. (Chrome 72'de, görünürlüğü aynı kaynaktaki tüm çerçevelere genişletmek için geçici bir geçici çözümümüz var. Kullanıcı etkinleştirmesini alt çerçevelere açıkça iletme yöntemine sahip olduğumuzda bu geçici çözümü kaldıracağız.)
  • Etkinleştirme denetimine sahip bir API, etkin bir çerçeveden ancak bir etkinlik işleyici kodunun dışından çağrıldığında, kullanıcı etkinleştirme durumu "etkin" olduğu sürece (ör. süresi dolmamış veya tüketilmemiş) çalışır. Kullanıcı Etkinleştirme v2'den önce bu işlem koşulsuz olarak başarısız olurdu.
  • Süre sonu zaman aralığı içinde kullanılmayan birden fazla kullanıcı etkileşimi, son etkileşime karşılık gelen tek bir etkinleştirmeye birleştirilir.

Etkinleştirme denetimine sahip API'lerde tutarlılık örnekleri

Aşağıda, User Activation v2'nin etkinleştirme denetimine sahip API'lerin davranışını nasıl tutarlı hale getirdiğini gösteren pop-up pencereler (window.open() kullanılarak açılır) içeren iki örnek verilmiştir.

Zincirlenmiş setTimeout() aramaları

Bu örnek, setTimeout() demosundan alınmıştır. Bir click işleyici bir pop-up'ı bir saniye içinde açmaya çalışırsa kodun gecikmeyi nasıl "oluşturduğu" fark etmeksizin işlemin başarılı olması beklenir. Kullanıcı Etkinleştirme v2 bu beklentiyi karşılar. Bu nedenle, aşağıdaki olay işleyicilerin her biri click'te bir pop-up açar (100 ms gecikmeyle):

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

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

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

Kullanıcı Etkinleştirme v2 olmadan, test ettiğimiz tüm tarayıcılarda ikinci etkinlik işleyici başarısız olur. (Bazı durumlarda ilk yöntem bile başarısız olur.)

Alanlar arası postMessage() çağrıları

postMessage() demo sürümümüzden bir örnek aşağıda verilmiştir. Kaynaklar arası alt çerçevedeki bir click işleyicinin doğrudan üst çerçeveye iki mesaj gönderdiğini varsayalım. Üst çerçeve, aşağıdaki mesajlardan birini aldığında bir pop-up açabilmelidir (ancak her ikisini birden alamaz):

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

Kullanıcı Etkinleştirme v2 olmadan üst çerçeve, ikinci mesajı aldıktan sonra pop-up açamaz. İlk mesaj bile başka bir kaynaktan gelen çerçeveye "zincirlenirse" (yani ilk alıcı mesajı başka bir alıcıya yönlendirirse) başarısız olur.

Bu, hem orijinal biçimde hem de zincirleme ile User Activation v2 ile çalışır.