1 poin oleh GN⁺ 2024-11-19 | 1 komentar | Bagikan ke WhatsApp
  • Tombol More di situs web BBC UK gagal menangani klik hanya pada lingkungan kerja dari rumah tertentu; bug UI yang tampak biasa ternyata merupakan masalah sistem koordinat multi-monitor
  • Jika monitor eksternal ditempatkan di atas·kiri monitor utama, nilai screenX dan screenY pada event click di Chrome dan Firefox bisa menjadi negatif
  • Kode lama menentukan klik pointer dengan event.screenX > 0 || event.screenY > 0, sehingga klik dengan koordinat negatif tidak dianggap sebagai klik mouse
  • Perbaikannya sederhana: bukan memeriksa apakah screenX dan screenY lebih besar dari 0, melainkan apakah keduanya bukan 0, menjadi bentuk event.type === 'click' && (event.screenX!== 0 || event.screenY!== 0)
  • Meski sudah melewati unit test, Puppeteer, pengujian manual, dan pengujian teknologi bantu, bug seperti ini tetap bisa tersisa karena ambiguitas spesifikasi UI Events dan asumsi koordinat multi-monitor

Bug navigasi BBC yang hanya tereproduksi di lingkungan tertentu

  • Bilah navigasi di situs web BBC UK membuka menu ketika pengguna mengaktifkan tombol More
  • Tombol ini menggunakan event click, dan event tersebut dapat dipicu bukan hanya oleh mouse, tetapi juga oleh sentuhan serta tombol Enter dan Space pada keyboard
  • Seorang anggota tim mengalami masalah hanya saat menggunakan laptop kerja di rumah; laptop yang sama berfungsi normal ketika digunakan di kantor
  • Di rumah pun, kegagalan hanya terjadi ketika jendela browser berada di monitor eksternal; di layar laptop, tombol berfungsi normal
  • Ketika masalah terjadi, alih-alih handler JavaScript membuka menu, menu terbuka melalui perilaku fallback tanpa JavaScript
  • Masalah yang sama tidak muncul di Safari

Kondisi reproduksi adalah posisi monitor

  • Tim mempersempit kondisi reproduksi dengan memeriksa elemen apa di lingkungan rumah yang memicu masalah
  • Monitor eksternal ditempatkan di atas layar laptop, dan masalah berhenti ketika penempatan ini diubah di pengaturan OS
  • Anggota tim lain juga dapat mereproduksi bug dengan menyamakan penempatan monitor di OS dengan cara yang sama
  • Dua kondisi yang teridentifikasi di awal investigasi adalah:
    • Masalah tidak terjadi di Safari
    • Masalah terjadi ketika monitor eksternal berada di atas dan kiri monitor utama

Koordinat negatif pada screenX, screenY

  • Saat event click pada tombol More diperiksa dengan console.log, nilai screenX dan screenY di Chrome dan Firefox muncul sebagai negatif
  • Event click, apa pun input yang memicunya, adalah salah satu jenis PointerEvent, sehingga objek event memuat informasi mouse atau pointer sentuh yang memicu klik
  • screenX dan screenY menunjukkan koordinat titik yang diklik pada layar dalam satuan piksel
  • Dalam spesifikasi DOM UI Events, tidak terlihat informasi spesifik tentang apakah properti tersebut dapat bernilai negatif
  • Perbedaan antara Safari dan Chrome·Firefox menunjukkan bahwa cara browser merepresentasikan koordinat layar dalam konfigurasi multi-monitor bisa berbeda
  • Masalah interoperabilitas ini telah dilaporkan ke tim WebKit

Perbedaan cara browser menangani koordinat multi-monitor

  • Dalam konfigurasi multi-monitor, sistem koordinat layar browser memperlakukan beberapa monitor seperti satu layar besar
  • Jika ada dua monitor 800px yang ditempatkan secara horizontal, rentang koordinat x bisa menjadi 0 hingga 1600
  • Di Safari, rentang koordinat tampaknya selalu berupa rentang positif yang dimulai dari monitor paling kiri atas
  • Di Chrome dan Firefox, koordinat tampaknya dihitung relatif terhadap monitor utama, sehingga layar yang berada di atas atau kiri monitor utama dapat menghasilkan koordinat negatif
  • Bug ini hanya terjadi ketika screenX dan screenY bernilai negatif

Kode bermasalah dan perbaikannya

  • isInvokedByMouse pada kode bermasalah mencoba memastikan apakah event click dipicu oleh mouse atau pointer sentuh dengan memeriksa apakah screenX dan screenY bernilai positif
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...
  }
};
  • Kode ini mengasumsikan bahwa screenX dan screenY dari event click yang dipicu pointer akan berupa bilangan positif
  • Ketika pengguna mengklik tombol More pada monitor dengan koordinat layar negatif, event handler tidak mengakui klik tersebut, lalu jatuh ke perilaku default tautan More
  • Perbaikannya adalah memeriksa apakah screenX dan screenY bukan 0, alih-alih apakah lebih besar dari 0
const isInvokedByMouse = event =>
  event.type === 'click' && (event.screenX !== 0 || event.screenY !== 0);
  • Dengan perubahan ini, pengguna dengan penempatan multi-monitor yang tidak umum pun dapat menggunakan bilah navigasi situs web BBC

Masalah desain yang tersisa dan refactoring lanjutan

  • Perbaikannya sendiri sederhana, tetapi masih ada bagian yang janggal dalam kode
  • Tidak perlu memeriksa apakah click dipicu oleh mouse atau keyboard, dan event handler menjadi rumit karena juga menangani event keydown
  • Perlu berhati-hati terhadap asumsi yang dibuat tentang perilaku API, dan ketidakjelasan spesifikasi tentang apakah screenX dan screenY bisa bernilai negatif juga menyembunyikan masalah ini
  • Kode ini telah melewati unit test, pengujian Puppeteer, serta pengujian manual menggunakan berbagai browser, perangkat, dan alat teknologi bantu, tetapi bugnya tidak ditemukan
  • Dalam pembaruan pada 19 November 2024, komponen navigasi kemudian telah di-refactor dan event handler tombol menu juga berubah besar
  • Tulisan lanjutan menjawab cara refactoring dan pertanyaan yang sering muncul: How I refactored the BBC navigation bar and a follow-up FAQ

1 komentar

 
GN⁺ 2024-11-19
Komentar Hacker News
  • Sebagai tambahan bagi yang belum sampai mengeklik laporan bug WebKit: seorang pengembang WebKit bertanya kepada BBC mengapa akan berguna jika bisa mendeteksi apakah sebuah event berasal dari keyboard, dan penulis menjawab bahwa interoperabilitas diperlukan karena kasus penggunaan terkait aksesibilitas
    Tombol menu pada navigation bar situs BBC UK berperilaku sedikit berbeda saat dibuka dengan pointer dan saat dibuka dengan keyboard. Event click selalu membuka menu, tetapi jika dibuka dengan pointer, fokus berpindah ke kontainer menu; jika dibuka dengan keyboard, fokus berpindah ke tautan pertama dalam menu tanpa animasi pembukaan menu. Event click bersifat independen dari perangkat, sehingga bagus untuk membuat pengalaman pengguna keyboard, dan pada keyboard hanya dipanggil dengan Space atau Enter. Jika memakai keydown, kita harus memeriksa sendiri apakah itu Space/Enter
    Sumber: https://bugs.webkit.org/show_bug.cgi?id=281430

    • Yang menarik, jika deskripsi kode dan bug WebKit ditafsirkan secara polos dalam bahasa Inggris, itu tidak cocok dengan struktur kode sebenarnya. Kode terkait adalah const isInvokedByMouse = event => event.screenX > 0 || event.screenY > 0; dan const isInvokedByKeyboard = event => isEnterKey(event) || isSpaceKey(event);; sekilas tampak seperti mencoba mengklasifikasikan event sebagai salah satu dari mouse atau keyboard
      Pada kenyataannya, muncul empat kategori: mouse tetapi bukan keyboard, keyboard tetapi bukan mouse, keduanya, dan bukan keduanya. Seperti bug aslinya, “bukan keduanya” ditangani dengan tidak semestinya, dan saya juga ragu apakah “keduanya” bekerja dengan benar. Kode tersebut seharusnya secara sengaja menangani fakta bahwa status keyboard dan status mouse adalah boolean terpisah, atau disusun agar eventSource mengembalikan kategori yang saling eksklusif seperti "keyboard", "mouse", "not sure"
    • Sepertinya ini bukan bug. Kesalahan pertama pengembangnya adalah mencoba membuat pengalaman pengguna yang berbeda untuk keyboard dan mouse
      Lebih baik mengikuti perilaku default dan merancang komponen yang bekerja untuk kedua kasus penggunaan. Dalam aksesibilitas, jangan mencoba terlalu pintar. Pada akhirnya solusinya menjadi hampir seperti hack, dan pendekatan seperti itu pasti akan pecah atau menimbulkan efek samping. Alasan jarang ada handle yang bagus untuk memperlakukan sesuatu secara berbeda dalam konteks aksesibilitas adalah karena sejak awal area itu memang tidak dimaksudkan untuk diperlakukan berbeda
    • Tulisan ini terasa membingungkan. Saya memahami bahwa BBC menginginkan perilaku yang sedikit berbeda tergantung apakah itu “klik” mouse atau “klik” keyboard, dan pada keyboard mereka ingin memfokuskan tautan pertama di menu tanpa animasi
      Pada saat yang sama, mereka juga menginginkan kenyamanan untuk hanya binding ke satu event. click memungkinkan hal ini, tetapi karena tidak ada cara untuk mengetahui apakah event terjadi karena klik mouse atau input keyboard, di Chrome mereka memakai heuristic yang rapuh: jika posisi mouse screenX=0, screenY=0, itu dianggap sebagai klik di titik asal atau trigger keyboard. Dari pengalaman mengerjakan proyek aksesibilitas, ini ide yang cukup buruk, dan kalau saya melihatnya di PR, saya akan meminta ditulis ulang. Idealnya memang browser berperilaku sama, tetapi masalah sebenarnya tampaknya adalah bahwa pada click yang dipicu keyboard, screenX dan screenY hampir tidak bermakna
      Idealnya, alih-alih mengeluarkan MouseEvent, seharusnya ada event yang lebih umum yang berlaku untuk keyboard maupun mouse, misalnya semacam "trigger", dan menyediakan informasi sumber trigger. Karena saat ini belum ada di spesifikasi dan butuh solusi segera, jauh lebih stabil dan tidak terlalu hacky untuk juga binding ke keydown, lalu jika click terjadi pada elemen yang sama bersama keydown, anggap itu sebagai input keyboard
    • Saya mengerti mengapa penulis membutuhkan screenX dan screenY, tetapi saya masih bertanya-tanya mengapa screenX harus mengembalikan koordinat layar nyata, bukan posisi internal renderer atau posisi halaman yang dirender seperti layerX, layerY
      Kebutuhan penulis bisa dipenuhi dengan posisi renderer, tanpa harus membocorkan posisi jendela browser ke setiap situs web yang dikunjungi
    • Pada kalimat “tidak ingin perilaku fokus dan animasi sedikit berbeda saat membuka menu tergantung apakah pengguna ‘mengklik’ dengan pointer atau ‘mengklik’ dengan keyboard”, saya merasa don’t mungkin salah ketik yang membuat maknanya menjadi kebalikan dari maksudnya
  • Pada bagian yang mengatakan “cukup mengubah isInvokedByMouse dari memeriksa apakah screenX dan screenY lebih besar dari 0 menjadi memeriksa apakah keduanya tidak sama dengan 0”, saya penasaran apa yang terjadi jika, meski sangat jarang, pengguna benar-benar mengklik mouse di posisi 0,0
    Saya tidak terlalu akrab dengan JS; apakah pemeriksaan != 0 benar-benar cara terbaik atau satu-satunya? Setelah membaca ulang, sepertinya kalimat bahwa event handler juga menangani keydown sehingga rumit dan perlu refactor lebih lanjut nanti, tetapi untuk saat ini perbaikan ini sudah cukup, sedikit banyak menjawab bagian ini

    • Mengecek posisi layar tampaknya seperti heuristic untuk menilai sifat event. Secara intuitif saya mungkin akan memakai instanceof MouseEvent, tetapi itu juga terasa berisiko atau seperti hack
      Saya bertanya-tanya mengapa mereka bergantung pada heuristic seperti ini. Mungkin karena toggleMenu dipakai di beberapa event handler, atau mungkin ada alasan lain yang spesifik pada codebase tersebut. Sulit menilai tanpa melihat gambaran lengkapnya. Sepertinya jawabannya ada di sini: https://news.ycombinator.com/item?id=42174177
    • Dalam kode yang sudah diperbaiki, mereka sudah memeriksa event.name == 'click'. Kalau begitu, saya tidak mengerti mengapa mereka ingin memfilter sebagian event click yang valid
    • Tidak begitu. Kita bisa melakukan media query/seleksi media untuk mengetahui apakah perangkat input utama adalah perangkat pointer, bahkan apakah perangkat itu punya akurasi tinggi, lalu memfilter berdasarkan itu
      Dulu saya pernah memakainya untuk memilih layout mana yang akan ditampilkan. Jika hanya ingin mendengarkan input sentuh, lakukan itu lalu panggil preventDefault pada event agar browser tidak membuat event click setelahnya. Atau, cukup kurangi repotnya dan tulis click handler saja
  • Patut diapresiasi bahwa BBC berinvestasi pada aksesibilitas lalu menemukan bug yang tidak menyenangkan ini. Namun mengapa industri masih belum bisa membuat dropdown yang terbuka secara konsisten untuk semua pengguna?
    Apakah aksesibilitas memang sesulit itu? Apakah BBC seharusnya memakai web framework atau web component yang sudah menangani hal seperti ini? Sebagai developer full-stack yang berfokus pada backend, saya berhati-hati saat menyentuh komponen browser. Ada banyak detail halus dalam perilakunya, dan implementasinya sudah teruji dalam waktu lama. Misalnya, membuat text box kustom tanpa meneliti secara mendalam perilaku text box di tiap platform tampak mudah gagal. Bahkan di situs perusahaan besar pun saya sering melihat copy/paste rusak dan karakter hilang. Saya tidak paham mengapa text box masih rusak pada 2024, dan sekarang React terasa arogan
    Secara pribadi, saya akan menanganinya dengan template sisi server, CSS framework seperti Bulma, dan JS seminimal mungkin. Ini tidak cocok untuk situs yang menuntut custom branding yang mulus, tetapi text box berfungsi dengan baik dan biaya pengembangannya juga tidak berlebihan. Saya tidak yakin apakah itu memenuhi standar aksesibilitas BBC

    • Saya tidak tahu jawaban untuk semua pertanyaan, tetapi untuk “apakah aksesibilitas sesulit itu”, saya bisa menjawab dengan tegas: ya
      Contoh nyatanya adalah modal. Jika Anda tidak memiliki gangguan penglihatan, Anda bisa melihat ada kotak putih di atas area abu-abu “jangan disentuh”, dengan komponen UI mengambang di dalamnya. Jika memakai screen reader, tidak ada jaminan Anda menerima informasi itu. Saat berpindah antar elemen UI dengan tab lalu kembali ke bagian paling atas kotak, apakah screen reader tertentu akan memberi tahu hal itu? Apakah ia akan mencantumkan elemen interaktif yang tersedia? Apakah urutannya sama seperti screen reader lain? Bagaimana di ponsel, bagaimana di Mac? Apakah screen reader dan browser akan melaporkan elemen input dengan benar, atau diam-diam membiarkan pengguna keluar dari modal dan kembali ke area situs lainnya?
      Dalam aksesibilitas, kita tidak bisa percaya bahwa sistem operasi, browser, dan screen reader akan bekerja sama atau bertindak masuk akal pada situasi yang tepat. Pada 2019, saya harus melaporkan bug di VoiceOver + Safari yang membuat screen reader membaca blok teks RTL dengan urutan terbalik karena CSS margin bernilai negatif. Secara visual terlihat sebagai 9/10/2019, tetapi di screen reader terdengar seperti “ten slash nine slash two-thousand-and-nineteen”, dan sebagai solusi sementara kami harus memberi teks itu aria-hidden, lalu memasukkan tag p tak terlihat dengan urutan yang benar. Jadi ketika melihat kode aneh terkait aksesibilitas, kadang memang benar-benar tidak ada cara yang lebih baik. Bahkan jika Anda membongkar total codebase dan menjadikan aksesibilitas prioritas utama, semuanya bisa rusak dengan cara yang sulit dipahami begitu JAWS atau VoiceOver diperbarui
    • Setuju. Namun banyak masalah pada akhirnya muncul ketika user agent mengustomisasi elemen-elemen ini dengan cara yang sangat meragukan
      Secara umum tidak apa-apa, tetapi ada alasan file reset.css ada, dan dalam kasus ini tampaknya mereka mungkin memakai pendekatan yang lebih ekstrem untuk sepenuhnya menghindari masalah seperti ini. Saya sedang mencoba menebak alasan di balik keputusan mereka
  • Ini tampak seperti bug yang dibuat sendiri akibat heuristik yang keliru. Mereka mengasumsikan nilai screenX/Y positif berarti event mouse, dan kurangnya pelacakan/logging membuat investigasi menjadi lebih rumit
    Agak mengejutkan bahwa alih-alih memeriksa properti yang lebih tepat seperti pointerType yang disarankan komentar lain, solusi penulis justru menambahkan heuristik yang rapuh lagi. Seolah dari dua petunjuk terakhir disimpulkan bahwa saat memeriksa koordinat screenX dan screenY, mereka harus memeriksa nilai negatif juga, bukan hanya positif

    • Sebenarnya memang itu yang akan dilakukan. Kami akan segera menggabungkan kode yang menggunakan pointerId === -1, lalu fallback ke screenX === 0
      Sekitar 4 tahun lalu, saat kode ini pertama kali ditulis, belum semua browser menggunakan PointerEvent untuk click
  • Saya bahkan tidak paham mengapa sejak awal sebuah situs web bisa memperoleh posisi mouse dalam sistem koordinat layar

    • Saya mencoba mencari alasannya, tetapi tidak menemukan banyak. Fakta bahwa situs web bisa mengetahui posisi jendela browser lewat window.screenX/window.screenY, dan posisi klik juga bisa dilaporkan dalam sistem koordinat itu, terdengar tidak masuk akal di desktop
      TOR Browser tampaknya menyamarkan screenX dan screenY untuk menghindari fingerprinting. Saya penasaran apakah ada yang pernah melihat use case yang bagus untuk fitur ini. Yang terpikir hanya aplikasi dua jendela yang saling berinteraksi, atau situs yang perilakunya berubah tergantung posisi di layar virtual
    • Ini berguna saat membuat game yang menyusun grafik dari beberapa jendela browser kecil yang saling berinteraksi
      Contoh: https://youtu.be/3al8prbfK5o?si=loNtyqIfMFkppm5V
    • Karena selama 10 hari yang dialokasikan untuk mengembangkan JavaScript pada 1995, fitur itu mudah diimplementasikan, lalu setelah itu kompatibilitas mundur berlaku :(
    • Jika Anda merespons event klik, Anda mungkin ingin mengetahui koordinat posisi yang diklik. Ini terutama dipakai pada operasi click-drag untuk menghitung delta antar-event dan memperbarui posisi objek yang sedang diseret
      Saya tidak mengerti mengapa mereka memeriksa koordinat alih-alih event.type. Namun artikelnya sendiri adalah teka-teki yang bagus, dan saya bisa memahami situasi ketika melihat kode yang tidak saya tulis lalu bertanya, “mengapa penting koordinat klik tidak boleh 0?”, “tidak bisakah cukup periksa apakah event.target adalah tombol yang ingin diaktifkan?”, “mengapa memakai JavaScript padahal hal yang sama bisa dilakukan dengan tag details/summary?”
    • Dipakai untuk CAPTCHA tanpa JavaScript. Bekerja dengan baik, dan saat diklik hanya mengirim x dan y dari klik mouse
  • Mengapa sejak awal memfilter berdasarkan koordinat layar? Bagaimana jika pengguna memakai perangkat input alternatif yang tidak memiliki layar?
    Event click saja sudah menjadi sinyal yang cukup bahwa pengguna mencoba mengaktifkan menu. Saya tidak paham mengapa perlu menciptakan ulang roda

    • Menurut artikel, isInvokedByMouse memeriksa apakah koordinat screenX atau screenY bernilai positif untuk memastikan event click dipanggil oleh mouse atau pointer sentuh, bukan keyboard
      Mereka mencoba mendeteksi apakah itu aktivasi keyboard atau mouse, dan penulis tampaknya berasumsi bahwa koordinat layar dari event mouse akan selalu positif
  • Saya mengunggah satu tulisan blog lagi untuk menjelaskan konteks yang membuat orang penasaran dan menjawab pertanyaan. Di sana saya menjelaskan mengapa sejak awal memeriksa screenX === 0, mengapa saya menginginkan perilaku berbeda tergantung input keyboard dan mouse, serta bagaimana saya melakukan refaktor untuk mencegah insiden tambahan
    Semoga bermanfaat: https://www.joshtumath.uk/posts/2024-11-18-how-i-refactored-...

  • Apa cara yang benar untuk memastikan apakah itu klik mouse atau klik keyboard? Rasanya ingin menetapkan flag tingkat modul berdasarkan event yang paling baru terjadi: jika mousedown lebih baru, set isKeyboard=false, isMouse=true; jika keydown lebih baru, set sebaliknya
    Dengan begitu fungsi isInvokedByMouse dan isInvokedByKeyboard tidak diperlukan. Adakah cara yang lebih baik? Mengandalkan koordinat layar untuk ini terasa sangat mencurigakan dan seperti hack

  • Sangat menarik, tetapi saya tidak mengerti mengapa browser melaporkan koordinat yang berbeda tergantung monitor. Saya kira browser memperlakukan halaman web seolah-olah selalu berada di satu layar penuh, di display mana pun halaman itu berada
    Apakah ada alasan Web API perlu memiliki informasi seperti ini? Ini terlihat seperti risiko keamanan, kebocoran informasi, dan pelacakan

  • Bukankah ini masalah kemampuan pengembangan? Seharusnya yang dipakai bukan koordinat layar, melainkan koordinat viewport, dan dibaca dengan .clientX serta .clientY. Saya tidak paham mengapa nilai negatif di ruang layar dianggap bug
    https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_view/...