- BBC UK web sitesindeki More düğmesi, yalnızca belirli bir evden çalışma ortamında tıklamaları işleyemiyordu; sıradan görünen UI hatası aslında çoklu monitör koordinat sistemi sorunuydu
- Harici monitör ana monitörün üstüne ve soluna yerleştirildiğinde, Chrome ve Firefox’ta
click olayındaki screenX, screenY negatif olabiliyordu
- Mevcut kod, işaretçi tıklamasını
event.screenX > 0 || event.screenY > 0 ile ayırt ediyordu; bu yüzden negatif koordinatlı tıklamaları fare tıklaması olarak görmüyordu
- Düzeltme,
screenX, screenY değerlerinin 0’dan büyük olup olmadığına değil, 0 olup olmadığına bakacak kadar basitti; biçimi event.type === 'click' && (event.screenX!== 0 || event.screenY!== 0) oldu
- Birim testleri, Puppeteer, manuel testler ve yardımcı teknoloji testlerinden geçmiş olsa bile, UI Events spesifikasyonundaki belirsizlik ve çoklu monitör koordinat varsayımları nedeniyle böyle hatalar kalabiliyor
Yalnızca belirli bir ortamda yeniden üretilebilen BBC navigasyon hatası
- BBC UK web sitesindeki navigasyon çubuğu, kullanıcı More düğmesini etkinleştirdiğinde menüyü açan bir davranış gerçekleştiriyor
- Bu düğme
click olayını kullanıyor; bu olay yalnızca fareyle değil, dokunma ve klavyedeki Enter, Space ile de tetiklenebiliyor
- Ekipten biri sorunu yalnızca iş dizüstü bilgisayarını evde kullanırken yaşıyor, aynı dizüstü bilgisayarı ofiste kullandığında her şey normal çalışıyordu
- Evde de yalnızca tarayıcı penceresi harici monitördeyken başarısız oluyor, dizüstü ekranında düğme normal çalışıyordu
- Sorun oluştuğunda JavaScript işleyicisi menüyü açmak yerine, menü JavaScript olmayan fallback davranışıyla açılıyordu
- Safari’de aynı sorun görülmüyordu
Yeniden üretme koşulu monitör konumuydu
- Ekip, ev ortamında hangi unsurun soruna yol açtığını kontrol ederek yeniden üretme koşulunu daralttı
- Harici monitör dizüstü ekranının üstüne yerleştirilmişti; OS ayarlarında bu yerleşim değiştirildiğinde sorun durdu
- Başka bir ekip üyesi de OS’te monitör yerleşimini aynı şekilde ayarlayınca hatayı yeniden üretebildi
- Araştırmanın başında doğrulanan koşullar ikiydi
- Safari’de sorun oluşmuyor
- Harici monitör ana monitörün üstünde ve solunda olduğunda sorun oluşuyor
screenX, screenY için negatif koordinatlar
More düğmesinin click olayı console.log ile kontrol edildiğinde, Chrome ve Firefox’ta screenX, screenY değerleri negatif çıkıyordu
click olayı hangi girdiyle tetiklenmiş olursa olsun bir PointerEvent türü olduğundan, olay nesnesi tıklamayı oluşturan fare ya da dokunma işaretçisinin bilgilerini içeriyor
screenX, screenY, ekranda tıklanan noktanın koordinatlarını piksel cinsinden gösteriyor
- DOM UI Events spec içinde bu özelliklerin negatif olup olamayacağına dair somut bir bilgi görünmüyordu
- Safari ile Chrome·Firefox arasındaki fark, çoklu monitör yapılandırmalarında tarayıcıların ekran koordinatlarını ifade etme biçimlerinin farklı olabileceğini gösteriyor
- Bu birlikte çalışabilirlik sorunu WebKit ekibine bildirildi
Tarayıcılara göre çoklu monitör koordinat yaklaşımı farkı
- Çoklu monitör yapılandırmalarında tarayıcının ekran koordinat sistemi, birden çok monitörü tek bir büyük ekranmış gibi ele alıyor
- Yatay yerleştirilmiş iki adet 800px monitör varsa x koordinat aralığı 0’dan 1600’e kadar olabilir
- Safari’de koordinat aralığı her zaman en sol üstteki monitörden başlayan pozitif bir aralık gibi görünüyor
- Chrome ve Firefox’ta koordinatlar ana monitör temel alınarak hesaplanıyor gibi görünüyor; ana monitörün üstünde ya da solunda kalan ekranlarda negatif koordinatlar çıkabiliyor
- Bu hata yalnızca
screenX, screenY negatif olduğunda oluşuyordu
Asıl sorunlu kod ve düzeltme
- Sorunlu koddaki
isInvokedByMouse, click olayının fare veya dokunma işaretçisiyle tetiklenip tetiklenmediğini anlamak için screenX, screenY değerlerinin pozitif olup olmadığını kontrol ediyordu
const isInvokedByMouse = event => event.screenX > 0 || event.screenY > 0;
const isInvokedByKeyboard = event => isEnterKey(event) || isSpaceKey(event);
// ...
const toggleMenu = event => {
// ...
if (isInvokedByMouse(event) || isInvokedByKeyboard(event)) {
event.preventDefault();
// Do stuff to open the menu and move the focus...
}
};
- Bu kod, işaretçiyle tetiklenen bir
click olayında screenX, screenY değerlerinin pozitif olacağını varsayıyordu
- Kullanıcı negatif ekran koordinatlarına sahip bir monitörde
More düğmesine tıkladığında olay işleyicisi tıklamayı kabul etmiyor ve More bağlantısının varsayılan davranışına düşüyordu
- Düzeltme,
screenX, screenY değerlerinin 0’dan büyük olup olmadığına bakmak yerine 0 olup olmadığını kontrol etmekti
const isInvokedByMouse = event =>
event.type === 'click' && (event.screenX !== 0 || event.screenY !== 0);
- Bu değişiklikle, alışılmadık çoklu monitör yerleşimleri kullanan kullanıcılar da BBC web sitesinin navigasyon çubuğunu kullanabilir hale geldi
Kalan tasarım sorunu ve sonraki refaktör
- Düzeltmenin kendisi basitti, ancak kodda hâlâ tuhaf kısımlar kalmıştı
click olayının fareyle mi klavyeyle mi tetiklendiğini kontrol etmeye gerek yok; olay işleyicisi keydown olayını da ele aldığı için karmaşıklaşmış durumda
- API davranışı hakkında hangi varsayımlarda bulunulduğuna dikkat edilmeli;
screenX, screenY için negatif değerlerin çıkıp çıkamayacağı konusunda spesifikasyonun net olmaması da sorunu gizledi
- Bu kod birim testlerinden, Puppeteer testlerinden, farklı tarayıcılar·cihazlar·yardımcı teknoloji araçlarıyla yapılan manuel testlerden geçmişti; buna rağmen hata bulunmamıştı
- 19 Kasım 2024 tarihli düzeltmeyle birlikte navigasyon bileşeni daha sonra refaktör edildi ve
menu düğmesinin olay işleyicisi de büyük ölçüde değişti
- Devam yazısı, refaktör yöntemini ve sık sorulan soruları yanıtlıyor: How I refactored the BBC navigation bar and a follow-up FAQ
1 yorum
Hacker News yorumları
WebKit hata raporuna kadar tıklamamış olanlar için ek bilgi: Bir WebKit geliştiricisi BBC'ye bir olayın klavyeden gelip gelmediğini algılayabilmenin neden yararlı olduğunu sordu; yazar da erişilebilirlikle ilgili kullanım senaryoları nedeniyle birlikte çalışabilirliğe ihtiyaç olduğunu söyledi.
BBC Birleşik Krallık web sitesindeki gezinme çubuğu menü düğmesi, işaretçiyle açıldığında ve klavyeyle açıldığında biraz farklı davranıyor. Tıklama olayı menüyü her zaman açıyor; ancak işaretçiyle açıldığında odak menü kapsayıcısına taşınıyor, klavyeyle açıldığında ise menü açılış animasyonu olmadan menüdeki ilk bağlantıya odak taşınıyor.
clickolayı cihazdan bağımsız olduğu için klavye kullanıcı deneyimi oluştururken iyi; klavyede de yalnızca Space veya Enter ile çağrılıyor.keydownkullanılırsa bunun Space/Enter olup olmadığını bizzat kontrol etmek gerekiyor.Kaynak: https://bugs.webkit.org/show_bug.cgi?id=281430
const isInvokedByMouse = event => event.screenX > 0 || event.screenY > 0;veconst isInvokedByKeyboard = event => isEnterKey(event) || isSpaceKey(event);; ilk bakışta olayı fare veya klavye olmak üzere iki sınıftan birine ayırmaya çalışıyormuş gibi görünüyor.Gerçekte ise dört kategori ortaya çıkıyor: fare ama klavye değil, klavye ama fare değil, ikisi birden, hiçbiri. Özgün hatada olduğu gibi “hiçbiri” uygunsuz biçimde ele alınıyor; “ikisi birden” durumunun da düzgün çalışıp çalışmadığı şüpheli. Kodun, klavye olup olmama ile fare olup olmamanın ayrı Boolean değerleri olduğunu bilinçli şekilde ele alması ya da
eventSourceun"keyboard","mouse","not sure"gibi birbirini dışlayan kategoriler döndürecek şekilde kurgulanması gerekir.Varsayılan davranışa uymak ve iki kullanım senaryosunda da çalışan bir bileşen tasarlamak daha iyi. Erişilebilirlikte fazla akıllı davranmaya çalışmamak gerekir. Sonuçta çözüm hack'e yakın bir şeye dönüşmüş ve bu tür yöntemler kaçınılmaz olarak kırılır ya da yan etki üretir. Erişilebilirlik bağlamında farklı davranmak için iyi tutamakların nadir olmasının nedeni, en başta buranın farklı davranışlar için tasarlanmış bir alan olmamasıdır.
Aynı zamanda yalnızca tek bir olaya bağlanmanın kolaylığını da istiyorlar.
clickbunu mümkün kılıyor; ancak olayın fare tıklamasıyla mı yoksa klavye girdisiyle mi oluştuğunu bilmenin yolu olmadığı için Chrome'da fare konumuscreenX=0,screenY=0ise bunun başlangıç noktasına tıklama veya klavye tetiklemesi olduğunu varsayan kırılgan bir sezgisel yöntem kullanıyorlar. Erişilebilirlik projeleri yapmış biri olarak bu oldukça kötü bir fikir; bir PR'da görsem yeniden yazılmasını isterdim. Tarayıcıların aynı davranması elbette ideal olurdu, ama asıl sorun klavyeyle oluşanclickolayındascreenXvescreenYdeğerlerinin neredeyse anlamsız olması gibi görünüyor.İdeal olarak
MouseEventyaymak yerine hem klavye hem fare için geçerli daha genel bir olay, örneğin"trigger"gibi bir şey olmalı ve tetikleme kaynağı bilgisini sağlamalı. Bu şu an spesifikasyonda olmadığı için acil bir çözüm gerekiyorsa,keydowna da bağlanıp aynı öğedekeydownile birlikteclickoluşursa bunu klavye girdisi saymak çok daha sağlam ve daha az hack'vari olur.screenXvescreenYye ihtiyaç duymasını anlıyorum, amascreenXin neden render içi konum ya da render edilmiş sayfa konumu olanlayerX,layerYyerine gerçek ekran koordinatlarını döndürmesi gerektiği hâlâ soru işareti.Yazarın ihtiyacı render konumuyla da karşılanabilir; ayrıca ziyaret edilen her web sitesine tarayıcı penceresinin konumunu sızdırmaya gerek kalmaz.
don’t, niyetin tersine anlam oluşturan bir yazım hatası değil mi diye düşünüyorum.“
isInvokedByMouseınscreenXvescreenYnin 0'dan büyük olup olmadığını kontrol etmesini, yalnızca 0'a eşit olup olmadığını kontrol edecek şekilde değiştirmek yeterliydi” kısmında, çok nadir de olsa kullanıcı gerçekten 0,0 konumunda fareyle tıklarsa ne olacağını merak ediyorum.JS'ye aşina değilim;
!= 0kontrolü gerçekten en iyi ya da tek yol mu? Tekrar okuyunca, olay işleyicisininkeydownı da işlediği için karmaşık olduğu ve ileride daha fazla refactor gerektiği, ama şimdilik bu düzeltmenin yeterli olduğu cümlesi bu noktayı bir ölçüde ele alıyor gibi.instanceof MouseEventkullanılacağını düşünürdüm, ama bu da riskli ya da hack gibi hissettiriyor.Neden böyle bir sezgisel yönteme dayandıkları merak konusu.
toggleMenunun birden fazla olay işleyicisinde kullanılması nedeniyle olabilir ya da kod tabanına özgü başka durumlar olabilir. Resmin tamamını bilmeden yargılamak zor. Yanıt burada gibi: https://news.ycombinator.com/item?id=42174177event.name == 'click'kontrol ediliyor. Öyleyse neden bazı normal tıklama olaylarını elemek istediklerini anlamıyorum.Bunu daha önce hangi düzenin gösterileceğini seçmek için kullanmıştım. Yalnızca dokunma girdisini dinlemek istiyorsanız bunu yapıp ardından olayda
preventDefaultçağırarak tarayıcının devamındaclickolayı oluşturmasını engelleyebilirsiniz. Ya da zahmete girmeyip sadece bir tıklama işleyicisi yazabilirsiniz.BBC’nin erişilebilirliğe yatırım yaparken can sıkıcı bir hata keşfetmiş olması takdire değer. Ama sektör neden hâlâ tüm kullanıcılar için tutarlı biçimde açılan bir dropdown’u doğru düzgün yapamıyor?
Erişilebilirlik gerçekten bu kadar zor mu? BBC’nin zaten bu tür şeyleri halleden bir web framework’ü ya da web component kullanması mı gerekirdi? Backend ağırlıklı bir full-stack geliştirici olarak tarayıcı component’lerine dokunurken temkinliyim. Davranışta çok ince noktalar var ve implementasyonlar uzun süredir sınanmış durumda. Örneğin özel bir metin kutusu yaparken platforma özgü metin kutusu davranışlarını derinlemesine araştırmamak kolayca başarısızlığa götürecek gibi görünüyor. Büyük şirket sitelerinde bile kopyala/yapıştırın bozulduğunu ve karakterlerin eksildiğini sık sık görüyorum. 2024’te metin kutusunun neden bozulduğunu anlamıyorum; React de artık kibirli hissettiriyor.
Kişisel olarak bunu sunucu tarafı template’ler, Bulma gibi bir CSS framework’ü ve minimum JS ile çözmeye çalışırdım. Pürüzsüz özel branding isteyen siteler için uygun değil, ama metin kutuları düzgün çalışıyor ve geliştirme maliyeti de aşırı değil. BBC’nin erişilebilirlik standartlarını karşılayıp karşılamadığından emin değilim.
Somut örnek olarak modal var. Görme engeliniz yoksa gri bir “dokunma” alanının üzerinde beyaz bir kutu ve onun içinde yüzen UI component’leri görürsünüz. Screen reader kullanıyorsanız bu bilgiyi alacağınızın garantisi yoktur. Tab ile UI öğeleri arasında gezinip kutunun en üstüne geri geldiğinizde belirli bir screen reader bunu haber verecek mi? Kullanılabilir etkileşim öğelerini listeleyecek mi? Başka bir screen reader ile aynı sırada mı listeleyecek? Telefonda, Mac’te ne olacak? Screen reader ve tarayıcı input öğesini doğru raporlayacak mı, yoksa kullanıcının modaldan çıkıp sitenin geri kalanına dönmesine sessizce izin mi verecek?
Erişilebilirlikte işletim sistemi, tarayıcı ve screen reader’ın işbirliği yapacağına ya da uygun durumda makul davranacağına güvenemezsiniz. 2019’da VoiceOver + Safari’de negatif CSS margin yüzünden RTL metin bloğunu screen reader’ın ters sırada okuduğu bir hatayı bildirmem gerekmişti. Görsel olarak
9/10/2019görünüyordu ama screen reader’da “ten slash nine slash two-thousand-and-nineteen” gibi duyuluyordu; geçici çözüm olarak metniaria-hiddenyapıp doğru sırada görünmez birpetiketi eklemek zorunda kalmıştık. Bu yüzden erişilebilirlikle ilgili tuhaf kod gördüğünüzde bazen gerçekten daha iyi bir yol olmayabiliyor. Codebase’i tamamen tersyüz edip erişilebilirliği en öncelikli hale getirseniz bile JAWS ya da VoiceOver güncellendiği anda anlaşılması zor şekilde bozulabilir.Genel olarak fena değil, ama
reset.cssdosyalarının var olmasının bir nedeni var; burada da bu tür sorunları tamamen baypas etmek için daha uç bir yaklaşım kullanmış olmaları muhtemel görünüyor. Kararlarını anlamaya çalışıyorum.Bu, hatalı bir sezgisel kuraldan kaynaklanan, kendi kendine yaratılmış bir bug gibi görünüyor. Pozitif
screenX/Ydeğerleri varsa bunun fare olayı olduğunu varsaymışlar; izleme/loglama eksikliği de incelemeyi daha karmaşık hale getirmiş.Diğer yorumların önerdiği daha uygun özellik olan
pointerType’ı kontrol etmek yerine, yazarın çözümünün sallantılı sezgisel kurala daha fazla yama eklemek olması biraz şaşırtıcı. Sanki son iki ipucundan,screenXvescreenYkoordinatlarını kontrol ederken yalnızca pozitifleri değil negatifleri de kontrol etmek gerektiği sonucuna varılmış gibi.pointerId === -1kullanıp ardındanscreenX === 0’a fallback yapan kodu merge edeceğim.Bu kodun ilk yazıldığı yaklaşık 4 yıl önce tüm tarayıcılar
clickiçin PointerEvent kullanmıyordu.En başta bir web sitesinin neden ekran koordinat sistemindeki fare konumunu elde edebildiğini anlamıyorum.
window.screenX/window.screenY’yi bilebilmesi ve tıklama konumunun da bu koordinat sisteminde raporlanabilmesi masaüstünde mantıksız geliyor.TOR Browser parmak izi takibini önlemek için
screenXvescreenYdeğerlerini gizliyor gibi görünüyor. Bu özelliğin iyi bir kullanım örneğini görüp görmediğinizi merak ediyorum. Aklıma birbiriyle etkileşen çift pencereli uygulamalar ya da davranışı sanal ekrandaki konuma göre değişen siteler geliyor.Örnek: https://youtu.be/3al8prbfK5o?si=loNtyqIfMFkppm5V
Neden
event.type’ı kontrol etmek yerine koordinatları kontrol ettiklerini anlamıyorum. Yine de yazının kendisi iyi bir bulmaca; benim yazmadığım koda bakıp “tıklama koordinatının 0 olmaması neden önemli?”, “sadeceevent.target’ın etkinleştirmek istediğin buton olup olmadığını kontrol edemez misin?”, “details/summaryetiketleriyle aynı işi yapabiliyorken neden JavaScript kullanıyorsun?” diye sormak zorunda kalma hâli tanıdık geliyor.En başta neden ekran koordinatlarına göre filtreleme yapılıyor? Kullanıcı ekransız alternatif bir giriş cihazı kullanıyorsa ne olacak?
clickevent’i tek başına kullanıcının menüyü etkinleştirmek istediğine dair yeterli sinyaldir. Neden tekerleği yeniden icat ettiklerini anlamıyorum.isInvokedByMouse,clickevent’inin klavye yerine fare veya dokunmatik pointer tarafından tetiklenip tetiklenmediğini anlamak içinscreenXya dascreenYkoordinatının pozitif olup olmadığını kontrol ediyordu.Klavye aktivasyonu mu fare aktivasyonu mu olduğunu tespit etmeye çalışmışlar; yazar da fare event’inin ekran koordinatlarının her zaman pozitif olacağını varsaymış.
İnsanların merak ettiği bağlamı açıklamak ve soruları yanıtlamak için bir blog yazısı daha yayımladım. Neden en başta
screenX === 0kontrolünü yaptığımı, neden klavye ve fare girdisine göre farklı davranış istediğimi ve ek kazaları önlemek için bunu nasıl refactor ettiğimi anlattım.Umarım faydalı olur: https://www.joshtumath.uk/posts/2024-11-18-how-i-refactored-...
Bunun fare tıklaması mı klavye tıklaması mı olduğunu anlamanın doğru yolu nedir? En son gerçekleşen olaya göre modül düzeyinde bir bayrak ayarlamak;
mousedowndaha yeniyseisKeyboard=false,isMouse=true,keydowndaha yeniyse tersini yapmak isteyesim geliyor.Böylece
isInvokedByMouseveisInvokedByKeyboardfonksiyonlarına gerek kalmaz. Daha iyi bir yöntem var mı? Bunu ekran koordinatlarına bağlı yapmak bana çok şüpheli ve hack gibi geliyorevent.detail[1], klavye “tıklaması” için 0, işaretçi tıklaması için 1’dir.1: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/det...
Çok ilginç, ama tarayıcının neden monitöre göre farklı koordinatlar raporladığını bilmiyorum. Tarayıcının, web sayfası hangi ekranda olursa olsun onu tüm ekran gibi ele aldığını sanıyordum.
Web API’nin bu tür bilgilere sahip olması için bir neden var mı? Bu güvenlik, bilgi sızıntısı ve izleme riski gibi görünüyor
Bu bir geliştirici yetkinliği sorunu değil mi? Ekran koordinatları değil, viewport koordinatları kullanılmalıydı ve
.clientXile.clientYüzerinden okunmalıydı. Ekran uzayında negatif değerlerin çıkmasının neden bug olduğunu anlamıyorum.https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_view/...