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 ve tam ekran gibi hassas API'leri kötüye kullanmasını önlemek için tarayıcılar, bu API'lere erişimi kullanıcı etkinleştirme yoluyla kontrol eder. Kullanıcı etkinleştirme, kullanıcı işlemleriyle ilgili olarak göz atma oturumunun durumudur: "Etkin" durum genellikle kullanıcının o anda sayfayla etkileşimde bulunduğu veya sayfa yüklendiğinden beri bir etkileşim tamamladığı anlamına gelir. Kullanıcı hareketi, aynı fikir için popüler olsa da yanıltıcı bir terimdir. Örneğin, bir kullanıcının yaptığı kaydırma veya vurma hareketi bir sayfayı etkinleştirmez ve dolayısıyla, komut dosyası açısından kullanıcı etkinleştirmesi değildir.

Günümüzde başlıca tarayıcılar, kullanıcı etkinleştirmenin etkinleştirmeyle korunan API'leri kontrol etme şekli konusunda oldukça farklı davranışlar göstermektedir. Chrome'da ise jeton tabanlı bir model temel alınıyordu. Bu modelin, etkinleştirme korumalı tüm API'lerde tutarlı bir davranış tanımlayamayacak kadar karmaşık olduğu ortaya çıktı. Örneğin Chrome, etkinleştirme korumalı API'lere postMessage() ve setTimeout() çağrıları üzerinden eksik erişime izin veriyordu. Promises, XHR, Oyun Kumandası etkileşimi vb. ile kullanıcı etkinleştirme desteklenmiyordu. Bu hatalardan bazılarının popüler olmasına rağmen uzun süredir var olan hatalar olduğunu unutmayın.

Chrome sürüm 72'de, etkinleştirme kapılı tüm API'ler için kullanıcı etkinleştirme kullanılabilirliğini tamamlayan Kullanıcı Etkinleştirme v2 sürümünü sunmaktadır. Bu sayede, yukarıda bahsedilen (ve MessageChannels gibi birkaç tane daha) tutarsızlıkları çözeriz. Bu tutarsızlıkların, kullanıcı etkinleştirmesinde web geliştirmeyi kolaylaştıracağını düşünüyoruz. Ayrıca yeni uygulama, uzun vadede tüm tarayıcıları bir araya getirmeyi hedefleyen, önerilen yeni bir spesifikasyon için bir referans uygulaması da sağlamaktadır.

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

Yeni API, çerçeve hiyerarşisindeki her window nesnesinde iki bitlik kullanıcı etkinleştirme durumunu korur: geçmiş kullanıcı etkinleştirme durumu için bir sabit bit (bir çerçeve kullanıcı etkinleştirmesi gördüyse) ve geçerli 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, ayarlandıktan sonra karenin ömrü boyunca asla sıfırlanmaz. Geçici bit, her kullanıcı etkileşiminde ayarlanır ve bir süre bitim aralığından (yaklaşık bir saniye) sonra veya aktivasyon tüketen bir API'ye (ör. window.open()) yapılan bir çağrıyla sıfırlanır.

Farklı etkinleştirme kapılı API'lerin, kullanıcı etkinleştirmesinin farklı şekillerde dayandığını unutmayın. Yeni API, bu API'ye özgü davranışlardan hiçbirini değiştirmemektedir. Örneğin, window.open() kullanıcı etkinleştirmesini eskisi gibi tükettiğinden, bir çerçeve (veya alt çerçevelerinden herhangi biri) kullanıcı işlemi görmüşse Navigator.prototype.vibrate() etkili olmaya devam eder. Bu şekilde, kullanıcı etkinleştirme işlemi başına kullanıcı etkinleştirme başına yalnızca bir pop-up'a izin verilir.

Neler değişiyor?

  • Kullanıcı Etkinleştirme v2, çerçeve sınırları arasında kullanıcı etkinleştirme görünürlüğü kavramını resmileştirir: Belirli bir çerçeveyle kullanıcı etkileşimi, artık kökenlerinden bağımsız olarak tüm kapsayıcı çerçeveleri (ve yalnızca bu çerçeveleri) etkinleştirecektir. (Chrome 72'de, görünürlüğü aynı kaynak çerçevelerin tümüne genişletmek için geçici bir çözümümüz vardır. Kullanıcı etkinleştirmesini açık bir şekilde alt çerçevelere iletme yöntemini sunduğumuzda bu geçici çözümü kaldıracağız.
  • Etkinleştirme korumalı API, etkinleştirilen 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 kullanılmamış) olduğu sürece çalışır. Kullanıcı Etkinleştirme v2'den önce ise koşulsuz olarak başarısız oluyordu.
  • Süre sonu zaman aralığı içindeki birden fazla kullanılmayan kullanıcı etkileşimi, son etkileşime karşılık gelen tek bir etkinleştirmeyle birleşir.

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

Aşağıda, Kullanıcı Etkinleştirme v2'nin etkinleştirmeyle korunan API'lerin davranışını nasıl tutarlı hale getirdiğini gösteren pop-up pencereli (window.open() kullanılarak açılmış) iki örnek verilmiştir.

Zincirlenen setTimeout() arama

Bu örnek setTimeout() demomuzdan alınmıştır. Bir click işleyicisi bir saniye içinde 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. Bu nedenle, aşağıdaki etkinlik işleyicilerin her biri click uygulamasında (100 ms gecikmeyle) bir pop-up açar:

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

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

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

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

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

Bir örneği postMessage() demomuzda görebilirsiniz. Kaynaklar arası alt çerçevedeki bir click işleyicinin doğrudan üst çerçeveye iki mesaj gönderdiğini varsayalım. Üst çerçeve, şu mesajlardan birini aldığında pop-up açabilmelidir (ancak ikisi birden kullanılamaz):

// 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 bir pop-up açamaz. İlk mesaj bile başka bir çapraz kaynak çerçeveye "zincirlenmişse" (diğer bir deyişle, ilk alıcı mesajı bir başkasına yönlendiriyorsa) başarısız olur.

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