
A click-through overlay két hibamódja
A GeekBye ablaka mindenek fölött lebeg, és átengedi rajta a kattintásaidat — kivéve ott, ahol gombjai vannak. Ez egy kétoldalú szerződés, és a v1.8.5 meg a v1.8.14 azt mutatja, hogy néz ki, amikor az egyes oldalak eltörnek: az egyik kiadásban az overlay elnyelt egy rendszerablakot, a másikban ellopta a billentyűleütéseidet. A második győztes javítása kódtörlés volt.
A GeekBye fő ablaka nem normál ablak. Keret nélküli, átlátszó és mindig felül van — egy üveglap, amely bármi fölött lebeg, amit épp csinálsz, néhány ráfestett vezérlővel. Az érdekes mérnöki rész nem az, hogy lebeg; hanem az, hogy az egerednek egyenesen át kellene mennie az üvegen mindenhol, kivéve a vezérlőknél. Rákattintasz egy gombra az overlayen, és az overlay kapja a kattintást. Rákattintasz a mellette lévő üres helyre, és a kattintás az alatta lévő meeting-alkalmazásodon landol, mintha az overlay ott sem lenne.
Ez egy kétoldalú szerződés: fogadj bemenetet pontosan ott, ahol UI van, és légy teljesen láthatatlan a bemenet számára mindenhol máshol. Mindkét oldalnak minden pillanatban tartania kell, és az alkalmazás e részének minden bugja az egyik vagy a másik oldal megsértése. Két kiadás, két hónap különbséggel, a lehető legtisztább illusztráció — mert mindegyik más oldalt tört el.
A szerződés, mechanikusan
A megvalósítás egy hook, a useMousePassthrough, és kicsi. Az ablak alapból click-through. Egy globális mousemove figyelő minden mozdulatnál egyetlen kérdést tesz fel: a kurzor egy vezérlő fölött van? A vezérlők egy data-ui-region attribútummal vannak megjelölve, így a teszt szó szerint target.closest('[data-ui-region]'). Egy vezérlő fölött meghívja a setIgnoreMouseEvents(false)-t, és az ablak elkapja a kattintásokat. Átlátszó tér fölött meghívja a setIgnoreMouseEvents(true, { forward: true })-t, és a kattintások átesnek.
Az a forward: true a teherhordó részlet. Amint egy ablak figyelmen kívül hagyja az egéreseményeket, normál esetben teljesen abbahagyja azok fogadását — beleértve azokat a mousemove eseményeket, amelyekre a hooknak szüksége van, hogy észrevegye a kurzort, amint visszatér egy vezérlő fölé. A forward: true azt mondja az Electronnak, hogy mégis továbbítsa a mozdulatokat a figyelmen kívül hagyó ablak felé, hogy az újra fel tudja fegyverezni magát. Nélküle az overlay egyszer click-through-vá válna, és soha nem térne vissza. A hook ráadásul csak az állapot-átmeneteknél hív át az IPC-határon — a kurzor átlépi a határt a UI és az üveg között — nem a mozgás minden pixeljénél. Jegyezd meg ezt az optimalizálást; van benne egy lyuk, és a v1.8.5 beleesett.
v1.8.5: amikor az átlátszósági oldal törik el
A szerződés átlátszósági oldala az, hogy „légy láthatatlan a bemenet számára mindenhol, ahol nincs UI." A v1.8.5 azt mutatja, hogy néz ki, amikor az overlay túl jelenlévő — amikor olyan bemenetet kap el vagy blokkol, amelyet át kellett volna engednie. A kiváltó ok a macOS engedélyablakai voltak, és két különálló hiba torlódott egymásra.
Az overlay fizikailag a párbeszédablak fölött volt. Egy always-on-top ablaknak van egy szintje, és a GeekBye overlaye a legmagasabbon ült, amit a macOS kínál — a screen-saver-en. Ez szinte minden fölött van, beleértve a rendszer saját Microphone és Screen Recording engedélyablakait is. Így az onboarding során, pontosan abban a pillanatban, amikor a macOS engedélyt kért, a párbeszédablak az overlay alatt renderelődhetett, és láthatatlan lehetett. A javítás, a setPermissionMode, csak macOS-es, és az ellenintuitívat teszi: egy engedélykérés idejére lejjebb viszi az overlayt a floating szintre, majd utána visszaállítja. A visszaállításához pedig egy konkrét trükk kellett, amely pontosan a commitban van dokumentálva: a macOS "doesn't always visually re-raise a window when setting the same level it was previously at," így a kód egy köztes szinten lépteti át az ablakot — floating → status → screen-saver —, hogy rákényszerítse az ablakszervert a rétegződési sorrend újraszámolására. Nem beállítod a kívánt szintet; lépésről lépésre eljutsz hozzá.
Az overlay ette a párbeszédablak kattintásait. Az onboarding egy sötét, teljes ablakos hátteret fest, és a passthrough hook külön esetként kezeli: amíg az a háttér fent van, az egész ablakra kattintás-elkapást kényszerít, hogy a varázsló teljesen interaktív legyen. De a „kapj el minden kattintást" pontosan rossz, amikor egy rendszerablak épp egyet próbál begyűjteni. A javítás egy engedélykérés idejére eltávolítja a háttér jelölő attribútumát, így a hook kiesik az elkapási módból, és a párbeszédablak gombjai megkapják a kattintásaikat. Az overlay a saját vezérlőit interaktívan tartja; csak abbahagyja, hogy úgy tesz, mintha az üres üveg gomb volna.
A lyuk az optimalizálásban. Emlékezz, hogy a hook csak átmeneteknél cselekszik. Amikor az ablak elhagyta az elkapási módot, újra lefuttatta a hover-tesztet — de ha a kurzorod abban a pillanatban már egy vezérlőn ült, a „UI fölött vagyunk?" válasz már „igen" volt, így nem indult el semmilyen átmenet, és a click-through soha nem lett kikapcsolva. A vezérlő élőnek látszott, és halott volt. A javítás (1b6eb20, idézve) abbahagyja, hogy megbízzon az átmenetben: "when exiting onboarding mode, directly evaluate whether cursor is over a UI region and make the correct IPC call." Amikor módot váltasz, nem vársz a következő mozdulatra, hogy megmondja, hol a kurzor — ránézel.
És tedd az átengedést visszafordíthatóvá. A finom kockázat mindebben az, hogy az overlay most átmenetileg lemond a z-orderéről és a kattintás-elkapásáról, és valaminek vissza kell adnia őket. Ha a visszaállító hívás hibát dob — egy IPC-akadás, egy kérés közben lebontott ablak —, az alkalmazás beragadhat úgy, hogy az isRequestingPermission true-n ragad, és egy sötét háttér örökre elrejti az egész képernyődet. Ezért a kilépési útvonalat úgy csomagolták be, hogy az állapot-visszaállítás „mindig lefut," plusz egy több perces biztonsági időtúllépéssel és egy takarítással az ablak megsemmisülésekor. A szabály, ami ebből következik: valahányszor egy overlay átenged bemenetet, a visszavételnek egy finally-ben kell lennie, nem a boldog úton.
v1.8.14: amikor az elkapási oldal törik el
A szerződés másik oldala az, hogy „fogadj bemenetet ott, ahol UI van." A v1.8.14 azt mutatja, hogy néz ki, amikor az overlay túl megragadó — amikor a vele való interakció elveszi tőled annak az alkalmazásnak a beviteli fókuszát, amely valójában fontos neked.
A changelogban a tünet a fókuszlopás, és íme az őszinte változat, mert a git-történet tanulságosabb, mint a kiadási jegyzet. Az overlay gyorsbillentyű-kezelői — láthatóság kapcsolása Cmd+B-vel, a chat-ablak megnyitása, annak görgetése — mindegyik meghívta a mainWindow.focus()-t, macOS-en pedig az app.focus({ steal: true })-t, hogy biztosan az overlay legyen legelöl, amikor kiváltod. A gond az, hogy az overlay fókuszálása deaktiválja azt, ami előtte legelöl volt — a meetingedet, a szerkesztődet —, így egy nekik szánt billentyűleütés a semmibe, vagy rossz helyre landolhatott. Az overlay olyasmit ragadott meg, amire valójában nem volt szüksége.
Ez az utolsó rész az egész lecke, és a csapat a nehéz úton találta meg. A v1.8.14-gyé vált ág először kifejezetten a húzást próbálta megoldani — mert a húzásnak megvan e bug saját gyönyörű változata: amikor egy teljes képernyős overlayt húzol, az ablak elmozdul egy álló kurzor alatt, így a hover-modell azt hiszi, hogy a mutató „elhagyta" a vezérlőt, és húzás közben click-through-ra vált, elejtve a húzást. Így hozzáadtak egy dragLock-ot. Aztán megpróbálták az egész ablakot nem-fókuszálhatóvá tenni — ami eltörte a gépelést és a húzást, ezért visszaállították a fókuszálhatóságot, majd a nem-fókuszálhatóságot csak a szövegmezőkre szűkítették, majd kitépték a kapcsolót. Építettek egy custom drag IPC-t, hogy lecseréljék az Electron natív húzását. Hozzáadtak egy blur-váratlan-fókuszra megoldást. Egy hét ebből.
Aztán szinte az egészet törölték. A commit, ami szállított, visszavonta a custom draget, a fókuszálhatósági kapcsolót és a blur-kezelőt, és pontosan egy ötletet tartott meg: hagyd abba a focus() hívását. A felismerés, egy kódkommentbe írva, az, hogy az overlaynek eleve soha nem volt szüksége OS-fókuszra — "IPC messages (webContents.send) work without window focus." Az overlay közvetlenül a saját rendererét vezérli; a fókusz tiszta járulékos kár volt. A szállítható javítás nem az egy nap alatt megépített okos custom-drag újraírás volt. Az azoknak a fókuszhívásoknak a kivonása volt, amelyeknek nem lett volna szabad ott lenniük. (Érdemes kimondani nyíltan: a changelog azt mondja, "while dragging it," de ami valójában szállított, az a fókuszlopás eltávolítása volt a billentyű-gyorsbillentyű kezelőkből — a kifejezetten húzásra szánt gépezetet ugyanazon a héten kísérletezték ki és vonták vissza.)
A tét, szó szerint véve
Ha bizonyítékot akarsz arra, hogy ez a szerződés nem elméleti, nézd meg, mi történt hónapokkal később, amikor az overlay a rossz oldalán ragadt be. Egy jóval későbbi kiadásban a renderer-folyamat összeomlott, miközben az overlay elkapási módban volt — setIgnoreMouseEvents(false), az ablak minden kattintást megragadva —, és mivel nem volt élő UI, ami feloldja, a láthatatlan, egész asztalt lefedő ablak ott ült, és felfalt minden kattintást a felhasználó gépén, amíg az kényszerkilépést nem hajtott végre. Egy elkapási módban beragadt átlátszó ablak nem halott gomb; egy egész asztalra kiterjedő kattintáscsapda. (Az a maga diagnózisát kapta — lásd amikor az AI jegyzetelőd megáll a felvétellel a meeting közepén, amely egy nagyon más buggal párosítja ugyanabban a kiadásban.) Ez az egész probléma legélesebb megfogalmazása: ragaszd be a kapcsolót az egyik irányba, és a UI-d halott; beragadva a másik irányba, és az egész asztalod az.
Három dolog, amit ezek a kiadások tanítottak nekünk
- A click-through overlay kétoldalú szerződés, és a két oldal másképp bukik el. Túl átlátszó, és a saját vezérlőid elhalnak; túl megragadó, és elnyeled más alkalmazások bemenetét. A legtöbb overlay-bug csak az egyik e kettő közül, és annak megnevezése, melyik oldalon vagy, megmondja, hol keresd.
- A legmagasabb always-on-top szint nem díj. Minden fölött ülni azt jelenti, hogy azok fölött a rendszerablakok fölött ülsz, amelyeket a felhasználónak látnia kell. Tervezz arra, amikor az overlayednek udvariasan félre kell állnia — és tedd a visszalépést visszafordíthatóvá egy
finally-ben, mert egy félig befejezett átengedés rosszabb, mint soha nem átengedni. - A kivonás legyőzi az okoskodást. A v1.8.14 győztes javítása olyan
focus()hívások törlése volt, amelyekre az overlaynek soha nem volt szüksége, és amelyeket csak egy hét építés és a custom drag kezelés visszavonása után találtak meg. Amikor egy funkció folyton a platformmal harcol, az első kérdés, amit fel kell tenni, az, hogy el tudod-e távolítani azt, ami harcol.
A v1-történet előző fejezetéért lásd a kiadás CI-be helyezése, kétszer (v1.8.4); a teljes ívért pedig a szoftver tökéletesre szállításának anatómiája.