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

Mustaq Ahmed
Ali Poşet
Ali Polat

Kötü amaçlı komut dosyalarının pop-up veya tam ekran gibi hassas API'leri kötüye kullanmasını önlemek için tarayıcılar, kullanıcı etkinleştirmesiyle bu API'lere erişimi kontrol eder. Kullanıcı aktivasyonu, kullanıcı işlemleriyle ilgili bir göz atma oturumunun durumudur: "Etkin" durumu genellikle kullanıcının şu anda sayfayla etkileşimde bulunduğu veya sayfa yüklendiğinden beri bir etkileşim tamamladığını gösterir. Kullanıcı hareketi, popüler ancak aynı fikir için yanıltıcı bir terimdir. Örneğin, bir kullanıcının kaydırma veya çevirme hareketi bir sayfayı etkinleştirmediğinden, komut dosyası açısından bakıldığında kullanıcı etkinleştirmesi söz konusu değildir.

Günümüzde başlıca tarayıcılar, kullanıcı etkinleştirmenin etkinleştirme kapılı API'leri nasıl kontrol ettiği konusunda çok farklı davranışlar göstermektedir. Chrome'da uygulama, jeton tabanlı bir modele dayalıydı. Bu model, 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ı üzerinden etkinleştirme korumalı API'lere eksik erişime izin veriyordu. Kullanıcı etkinleştirme ise Promises, XHR, Gamepad etkileşimi vb. ile desteklenmiyordu. Bunlardan bazılarının popüler ancak uzun süredir var olan hatalar olduğunu unutmayın.

Chrome, sürüm 72'de Kullanıcı Etkinleştirme v2'yi göndererek etkinleştirme güvenlikli tüm API'ler için kullanıcı etkinleştirmesini eksiksiz hale getirir. Bu, yukarıda bahsedilen tutarsızlıkları (ve MessageChannels gibi birkaç tane daha) giderir. Bunun kullanıcı etkinleştirmesi çerçevesinde web geliştirmesini kolaylaştıracağını düşünüyoruz. Ayrıca yeni uygulama, uzun vadede tüm tarayıcıları bir araya getirmeyi amaçlayan, önerilen yeni bir spesifikasyon için bir referans 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 durumunu korur: geçmiş kullanıcı etkinleştirme durumu için sabit bir bit (bir çerçeve daha önce bir kullanıcı etkinleştirmesi görmüşse) ve mevcut durum için geçici bir bit (bir çerçeve, yaklaşık bir saniye içinde kullanıcı etkinleştirmesi gördüyse). Yapışkan bit, ayarlanan karenin ömrü 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 tüketen bir API'ye (ör. window.open()) yapılan bir çağrıyla sıfırlanır.

Etkinleştirme kapılı farklı API'lerin, kullanıcı etkinleştirmesine farklı şekillerde bağlı olduğunu unutmayın. Yeni API, bu API'ye özel davranışların hiçbirini değiştirmez. Örneğin, window.open() kullanıcı etkinleştirmesini eskisi gibi kullandığı için kullanıcı etkinleştirmesi 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örmüşse etkin olmaya devam eder ve bu böyle devam eder.

Neler değişecek?

  • Kullanıcı Etkinleştirme v2, çerçeve sınırları içindeki kullanıcı etkinleştirme görünürlüğü kavramını resmileştirir: Belirli bir çerçeveyle olan bir kullanıcı etkileşimi artık kaynağından bağımsız olarak içeren tüm çerçeveleri (yalnızca bu çerçeveleri) etkinleştirir. (Chrome 72'de, görünürlüğü tüm aynı kaynak çerçeveleri içine alacak şekilde genişletmek için geçici bir çözümümüz vardır. Kullanıcı aktivasyonunu alt çerçevelere açık bir şekilde iletmenin bir yolu olduğunda bu geçici çözümü kaldıracağız.)
  • Etkinleştirme kapılı API, etkinleştirilmiş bir çerçeveden ancak bir etkinlik işleyici kodunun dışından çağrıldığında, kullanıcı etkinleştirme durumu "etkin" olduğu (ör. süresi dolmamış veya tüketilmemiş) olduğu sürece çalışır. Kullanıcı Etkinleştirme v2'den önce, koşulsuz olarak başarısız olurdu.
  • Sona erme zaman aralığı içindeki birden çok kullanılmayan kullanıcı etkileşimi, son etkileşime karşılık gelen tek bir etkinleştirmeye dönüştürülür.

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

Aşağıda, Kullanıcı Etkinleştirme v2'nin etkinleştirme erişimli API'lerin davranışını nasıl tutarlı hale getirdiğini gösteren, window.open() kullanılarak açılan pop-up pencereli iki örneği bulabilirsiniz.

Zincirleme setTimeout() arama

Bu örnek, setTimeout() demomuzdan alınmıştır. click işleyicisi saniye içinde bir pop-up açmaya çalışırsa kodun gecikmeyi nasıl "oluşturduğundan" bağımsız olarak pop-up'ın başarılı olması beklenir. Kullanıcı Etkinleştirme v2 bu beklentiyi karşılar. Böylece, aşağıdaki etkinlik işleyicilerin her biri click ürününde 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, ikinci etkinlik işleyici test ettiğimiz tüm tarayıcılarda başarısız olur. (İlki bile bazı durumlarda başarısız olur.)

Web alanları arası postMessage() aramaları

postMessage() demomuzdan bir örneği aşağıda bulabilirsiniz. Çapraz kaynak alt çerçevesindeki bir click işleyicinin doğrudan üst çerçeveye iki mesaj gönderdiğini varsayalım. Üst çerçeve, bu mesajlardan biri (ancak ikisini birden değil) aldığında pop-up açabilmelidir:

// 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. Başka bir çapraz kaynak çerçeveye "zincirlenmiş"se (diğer bir deyişle, ilk alıcı mesajı bir başkasına iletirse) ilk mesaj bile başarısız olur.

Bu, hem orijinal biçimde hem de zincirle Kullanıcı Etkinleştirme v2 ile çalışır.