
Dua Mod Kegagalan Sebuah Overlay Click-Through
Tetingkap GeekBye terapung di atas segala-galanya dan membenarkan klik anda menembusinya — kecuali di tempat ia mempunyai butang. Itu satu kontrak dua sisi, dan v1.8.5 serta v1.8.14 ialah wajahnya apabila setiap sisi patah: satu release yang overlay-nya menelan sebuah dialog sistem, satu lagi yang ia mencuri ketukan kekunci anda. Penyelesaian yang menang bagi yang kedua ialah memadamkan kod.
Tetingkap utama GeekBye bukan sebuah tetingkap biasa. Ia tanpa bingkai, transparan, dan sentiasa di atas — sekeping kaca yang terapung di atas apa sahaja yang sedang anda lakukan, dengan beberapa kawalan terlukis di atasnya. Kejuruteraan yang menarik bukanlah bahawa ia terapung; ialah bahawa tetikus anda sepatutnya menembusi kaca itu terus di mana-mana kecuali pada kawalan. Klik sebuah butang pada overlay dan overlay mendapat kliknya. Klik ruang kosong di sebelahnya dan klik itu mendarat pada aplikasi mesyuarat anda di bawahnya, seolah-olah overlay langsung tiada di situ.
Itu ialah satu kontrak dengan dua sisi: terima input tepat di tempat ada UI, dan jadilah tidak kelihatan sepenuhnya kepada input di mana-mana sahaja yang lain. Kedua-dua sisi mesti bertahan pada setiap detik, dan setiap bug pada bahagian aplikasi ini ialah satu pelanggaran sisi yang satu atau yang lain. Dua release, berselang dua bulan, ialah ilustrasi paling bersih yang mungkin — kerana setiap satunya mematahkan sisi yang berbeza.
Kontrak itu, secara mekanikal
Pelaksanaannya ialah sebuah hook, useMousePassthrough, dan ia kecil. Tetingkap bersifat click-through secara lalai. Sebuah listener mousemove global bertanya satu soalan pada setiap gerakan: adakah kursor berada di atas sebuah kawalan? Kawalan ditandakan dengan atribut data-ui-region, jadi ujiannya secara harfiah ialah target.closest('[data-ui-region]'). Di atas sebuah kawalan, ia memanggil setIgnoreMouseEvents(false) dan tetingkap menangkap klik. Di atas ruang transparan, ia memanggil setIgnoreMouseEvents(true, { forward: true }) dan klik jatuh menembusi.
Si forward: true itu ialah butiran yang menanggung. Sebaik sahaja sebuah tetingkap mengabaikan event tetikus, ia biasanya berhenti menerimanya sama sekali — termasuk event mousemove yang diperlukan hook untuk menyedari kursor datang semula ke atas sebuah kawalan. forward: true memberitahu Electron untuk terus memajukan gerakan ke tetingkap yang mengabaikan itu walau bagaimanapun, supaya ia boleh mengaktifkan dirinya semula. Tanpanya, overlay akan menjadi click-through sekali dan tidak pernah kembali. Hook juga hanya memanggil merentasi sempadan IPC pada peralihan keadaan — kursor melintasi sempadan antara UI dan kaca — bukan pada setiap piksel gerakan. Ingat pengoptimuman itu; ada satu lubang di dalamnya, dan v1.8.5 jatuh ke dalamnya.
v1.8.5: apabila sisi transparansi patah
Sisi transparansi bagi kontrak ialah "jadilah tidak kelihatan kepada input di mana-mana sahaja yang tiada UI." v1.8.5 ialah wajahnya apabila overlay terlalu hadir — apabila ia menangkap atau menyekat input yang sepatutnya ia biarkan lalu. Pencetusnya ialah dialog kebenaran macOS, dan ada dua kegagalan berbeza yang bertindan satu di atas yang lain.
Overlay secara fizikal berada di atas dialog. Sebuah tetingkap sentiasa-di-atas mempunyai satu aras, dan overlay GeekBye duduk pada yang tertinggi yang ditawarkan macOS — screen-saver. Itu di atas hampir segala-galanya, termasuk dialog kebenaran Microphone dan Screen Recording milik sistem itu sendiri. Jadi semasa onboarding, tepat pada detik macOS meminta kebenaran, dialog boleh ter-render di bawah overlay dan tidak kelihatan. Penyelesaiannya, setPermissionMode, khusus-macOS dan melakukan perkara yang tidak intuitif: ia menurunkan overlay ke aras floating selama tempoh satu permintaan kebenaran, kemudian memulihkannya selepas itu. Dan memulihkannya memerlukan satu helah khusus, terdokumen tepat di dalam commit: macOS "doesn't always visually re-raise a window when setting the same level it was previously at," jadi kod melangkahkan tetingkap melalui satu aras perantara — floating → status → screen-saver — untuk memaksa window server mengira semula susunan tindanan. Anda tidak menetapkan aras yang anda mahu; anda berjalan menujunya.
Overlay memakan klik milik dialog. Onboarding melukis satu latar gelap yang memenuhi seluruh tetingkap, dan hook passthrough melayannya sebagai kes khas: selagi latar itu terpasang, ia memaksa penangkapan-klik pada seluruh tetingkap supaya wizard boleh diinteraksi sepenuhnya. Tetapi "tangkap setiap klik" tepat-tepat salah apabila sebuah dialog sistem sedang cuba mengumpul satu. Penyelesaiannya menanggalkan atribut penanda milik latar semasa satu permintaan kebenaran, supaya hook terkeluar daripada mod penangkapan dan butang-butang dialog mendapat klik mereka. Overlay mengekalkan kawalannya sendiri boleh diinteraksi; ia cuma berhenti berpura-pura bahawa kaca kosong itu sebuah butang.
Lubang di dalam pengoptimuman. Ingat bahawa hook hanya bertindak pada peralihan. Apabila tetingkap meninggalkan mod penangkapan, ia menjalankan semula ujian hover — tetapi jika kursor anda sudah berada di atas sebuah kawalan pada saat itu, jawapan kepada "adakah kita di atas UI?" sudah pun "ya," jadi tiada peralihan tercetus, dan click-through tidak pernah dinyahaktifkan. Kawalan itu kelihatan hidup padahal mati. Penyelesaiannya (1b6eb20, dipetik) berhenti mempercayai peralihan: "when exiting onboarding mode, directly evaluate whether cursor is over a UI region and make the correct IPC call." Apabila anda menukar mod, anda tidak menunggu gerakan seterusnya memberitahu anda di mana kursor berada — anda melihat.
Dan jadikan penyerahan itu boleh dibalikkan. Risiko halus dalam semua ini ialah bahawa overlay kini buat sementara melepaskan z-order-nya dan penangkapan-kliknya, dan sesuatu mesti memulangkan kedua-duanya. Jika panggilan pemulihan melontarkan ralat — satu tersekat IPC, satu tetingkap yang dirobohkan di tengah permintaan — aplikasi boleh terbiar dengan isRequestingPermission tersangkut true dan satu latar gelap menyembunyikan seluruh skrin anda, secara kekal. Jadi laluan keluar dibalut supaya set-semula-keadaan "sentiasa dilaksanakan," ditambah satu timeout keselamatan berbilang-minit dan satu pembersihan apabila tetingkap dimusnahkan. Peraturan yang terhasil daripada itu: setiap kali sebuah overlay menyerahkan input, pembatalan-penyerahan itu mesti berada di dalam satu finally, bukan di laluan bahagia.
v1.8.14: apabila sisi penangkapan patah
Sisi yang lain bagi kontrak ialah "terima input di tempat ada UI." v1.8.14 ialah wajahnya apabila overlay terlalu suka merebut — apabila berinteraksi dengannya merampas daripada anda fokus input aplikasi yang sebenarnya anda pedulikan.
Gejalanya di changelog ialah pencurian-fokus, dan inilah versi jujurnya, kerana sejarah git lebih memberi pengajaran daripada nota release. Handler pintasan milik overlay — menogol keterlihatan dengan Cmd+B, membuka tetingkap chat, men-scroll-nya — setiap satunya dahulu memanggil mainWindow.focus(), dan di macOS app.focus({ steal: true }), untuk memastikan overlay berada paling depan apabila anda mencetusnya. Masalahnya ialah bahawa memfokuskan overlay menyahaktifkan apa sahaja yang paling depan sebelumnya — mesyuarat anda, editor anda — jadi satu ketukan kekunci yang dimaksudkan untuk mereka boleh mendarat di mana-mana, atau di tempat yang salah. Overlay merebut sesuatu yang sebenarnya ia tidak perlukan.
Bahagian terakhir itulah keseluruhan pengajarannya, dan pasukan menemuinya dengan cara yang sukar. Branch yang menjadi v1.8.14 mula-mula cuba menyelesaikan seretan secara khusus — kerana menyeret mempunyai versinya sendiri yang cantik bagi bug ini: apabila anda menyeret sebuah overlay skrin-penuh, tetingkap bergerak di bawah kursor yang pegun, jadi model hover menyangka penuding "meninggalkan" kawalan dan berbalik ke click-through di tengah seretan, menjatuhkan seretan itu. Maka mereka menambah satu dragLock. Kemudian mereka cuba menjadikan seluruh tetingkap non-focusable — yang mematahkan menaip dan menyeret, jadi mereka memulihkan focusability, kemudian menyempitkan non-focusability hanya kepada input teks, kemudian mencabut toggle-nya. Mereka membina satu IPC drag tersuai untuk menggantikan drag native milik Electron. Mereka menambah blur-ketika-fokus-tak-dijangka. Seminggu begini.
Kemudian mereka memadamkan hampir kesemuanya. Commit yang dihantar me-revert custom drag, toggle focusable, dan handler blur, dan mengekalkan tepat satu idea: berhenti memanggil focus(). Kesedarannya, ditulis ke dalam satu komen kod, ialah bahawa overlay sejak mula langsung tidak pernah memerlukan fokus OS — "IPC messages (webContents.send) work without window focus." Overlay memandu renderer-nya sendiri secara terus; fokus ialah kerosakan sampingan semata-mata. Penyelesaian yang boleh dihantar bukanlah penulisan-semula custom-drag yang bijak yang dibina dalam sehari. Ia ialah menolak panggilan fokus yang sepatutnya tidak pernah ada di situ. (Berbaloi dikatakan secara terus terang: changelog berkata "while dragging it," tetapi yang sebenarnya dihantar ialah membuang pencurian-fokus daripada handler pintasan-papan-kekunci — jentera khusus-drag itu diterokai dan di-revert pada minggu yang sama.)
Apa yang dipertaruhkan, dibuat harfiah
Jika anda mahu bukti bahawa kontrak ini bukan akademik, lihat apa yang berlaku berbulan-bulan kemudian apabila overlay tersangkut di sisi yang salah padanya. Dalam sebuah release yang jauh kemudian, proses renderer terhempas semasa overlay berada dalam mod penangkapan — setIgnoreMouseEvents(false), tetingkap merebut semua klik — dan tanpa UI hidup untuk melepaskannya, tetingkap tidak kelihatan yang memenuhi seluruh desktop itu duduk di situ memakan setiap klik pada mesin pengguna sehingga mereka force-quit. Sebuah tetingkap transparan yang tersangkut dalam mod penangkapan bukanlah butang mati; ia ialah perangkap klik selebar desktop. (Yang satu itu mendapat diagnosisnya sendiri — lihat apabila AI notetaker anda berhenti merakam di pertengahan mesyuarat, yang memasangkannya dengan sebuah bug yang sangat berbeza dalam release yang sama.) Ia ialah kenyataan paling tajam bagi keseluruhan masalah: buat toggle tersangkut satu arah dan UI anda mati; tersangkut arah yang lain dan seluruh desktop anda yang mati.
Tiga perkara yang diajarkan oleh release-release ini kepada kami
- Sebuah overlay click-through ialah kontrak dua sisi, dan kedua-dua sisi gagal secara berbeza. Terlalu transparan dan kawalan anda sendiri mati; terlalu suka merebut dan anda menelan input aplikasi lain. Kebanyakan bug overlay hanyalah salah satu daripada dua itu, dan menamakan di sisi mana anda berada memberitahu anda di mana perlu mencari.
- Aras sentiasa-di-atas tertinggi bukanlah satu hadiah. Duduk di atas segala-galanya bermakna duduk di atas dialog sistem yang anda perlukan penggunanya melihat. Reka bentuk untuk saat overlay anda sepatutnya berundur selangkah dengan sopan — dan jadikan naik semula boleh dibalikkan di dalam satu
finally, kerana satu penyerahan yang separuh-siap lebih teruk daripada tidak pernah menyerah. - Penolakan mengalahkan kepintaran. Penyelesaian v1.8.14 yang menang ialah memadamkan panggilan
focus()yang overlay tidak pernah perlukan, ditemui hanya selepas seminggu membina dan me-revert pengendalian custom drag. Apabila sebuah ciri terus-menerus melawan platform, soalan pertama yang perlu ditanya ialah sama ada anda boleh membuang benda yang melakukan perlawanan itu.
Untuk bab sebelumnya dalam kisah v1, meletakkan release ke dalam CI, dua kali (v1.8.4); dan untuk keseluruhan lengkungnya, anatomi mengeluarkan perisian hingga ke kesempurnaan.