
Klikiläbiva ülekatte kaks tõrkerežiimi
GeekBye aken hõljub kõige peal ja laseb su klikkidel endast läbi minna — välja arvatud seal, kus tal on nupud. See on kahepoolne leping, ja v1.8.5 ning v1.8.14 näitavad, kuidas see välja näeb, kui kumbki pool puruneb: üks väljalase, kus ülekate neelas süsteemidialoogi, üks, kus see varastas su klahvivajutused. Teise võiduka paranduse oli koodi kustutamine.
GeekBye peaaken pole tavaline aken. See on raamita, läbipaistev ja alati pealmine — klaasileht, mis hõljub kõige kohal, mida sa parasjagu teed, mõne sellele joonistatud juhtnupuga. Huvitav insenertöö pole see, et see hõljub; see on see, et su hiir peaks minema otse klaasist läbi igal pool peale juhtnuppude. Klõpsa ülekattel nuppu ja ülekate saab klõpsu. Klõpsa selle kõrval tühja kohta ja klõps maandub su all olevas koosolekuäpis, nagu ülekatet poleks üldse olemas.
See on leping kahe poolega: võta sisendit täpselt seal, kus on UI, ja ole sisendile täiesti nähtamatu igal pool mujal. Mõlemad pooled peavad kehtima igal hetkel, ja iga viga selles rakenduse osas on ühe või teise poole rikkumine. Kaks väljalaset, kahe kuu vahega, on puhtaim võimalik näide — sest kumbki purustas erineva poole.
Leping, mehaaniliselt
Teostus on konks, useMousePassthrough, ja see on väike. Aken on vaikimisi klikiläbiv. Globaalne mousemove kuulaja esitab igal liigutusel ühe küsimuse: kas kursor on juhtnupu kohal? Juhtnupud on märgistatud data-ui-region atribuudiga, nii et test on sõna-sõnalt target.closest('[data-ui-region]'). Juhtnupu kohal kutsub see setIgnoreMouseEvents(false) ja aken püüab klõpse kinni. Läbipaistva ruumi kohal kutsub see setIgnoreMouseEvents(true, { forward: true }) ja klõpsud kukuvad läbi.
See forward: true on kandev detail. Kui aken kord hiiresündmusi ignoreerib, lakkab ta neid tavaliselt üldse vastu võtmast — sealhulgas mousemove-sündmusi, mida konks vajab, et märgata kursorit tagasi juhtnupu kohale tulemas. forward: true käsib Electronil liigutusi ignoreerivasse aknasse ikkagi edasi saata, et see saaks end uuesti valmis seada. Ilma selleta läheks ülekate korra klikiläbivaks ega tuleks kunagi tagasi. Konks kutsub üle IPC piiri ka ainult seisundi üleminekutel — kui kursor ületab piiri UI ja klaasi vahel — mitte igal liikumispikslil. Jäta see optimeerimine meelde; sellel on auk, ja v1.8.5 kukkus sinna sisse.
v1.8.5: kui läbipaistvuse pool puruneb
Lepingu läbipaistvuse pool on "ole sisendile nähtamatu igal pool, kus pole UI-d." v1.8.5 näitab, kuidas see välja näeb, kui ülekate on liiga kohalolev — kui see püüab või blokeerib sisendit, mille oleks pidanud läbi laskma. Vallandaja olid macOS-i loadialoogid, ja seal oli kaks eraldiseisvat tõrget üksteise otsa virnastatud.
Ülekate oli füüsiliselt dialoogi kohal. Alati-pealmisel aknal on tase, ja GeekBye ülekate istus kõrgeimal, mida macOS pakub — screen-saver. See on peaaegu kõige kohal, sealhulgas süsteemi enda Microphone ja Screen Recording loadialoogide kohal. Nii et onboarding'u ajal, täpselt sel hetkel, kui macOS küsis luba, võis dialoog renderduda ülekatte alla ja olla nähtamatu. Parandus, setPermissionMode, on ainult macOS-ile ja teeb ebaintuitiivse asja: see langetab ülekatte loataotluse ajaks floating tasemele ja taastab selle pärast. Ja selle taastamine vajas kindlat trikki, dokumenteeritud otse commitis: macOS "doesn't always visually re-raise a window when setting the same level it was previously at," nii et kood astub akna läbi vahepealse taseme — floating → status → screen-saver — et sundida aknaserverit virnastusjärjekorda ümber arvutama. Sa ei sea taset, mida tahad; sa kõnnid selleni.
Ülekate sõi dialoogi klõpse. Onboarding joonistab tumeda täisakna tausta, ja läbilaske-konks käsitleb seda erijuhtumina: kuni see taust on üleval, sunnib see klõpsupüüdmist kogu aknale, et viisard oleks täielikult interakteeritav. Aga "püüa iga klõps kinni" on täpselt vale, kui süsteemidialoog üritab üht koguda. Parandus eemaldab tausta markeratribuudi loataotluse ajaks, nii et konks kukub püüdmisrežiimist välja ja dialoogi nupud saavad oma klõpsud. Ülekate hoiab oma juhtnupud interakteeritavana; see lihtsalt lakkab teesklemast, et tühi klaas on nupp.
Auk optimeerimises. Pea meeles, et konks tegutseb ainult üleminekutel. Kui aken lahkus püüdmisrežiimist, käivitas see hõljumistesti uuesti — aga kui su kursor juba istus sel hetkel juhtnupul, oli vastus küsimusele "kas oleme UI kohal?" juba "jah," nii et ühtki üleminekut ei vallandunud ja klikiläbivust ei keelatud kunagi. Juhtnupp nägi elus välja ja oli surnud. Parandus (1b6eb20, tsiteeritud) lakkab üleminekut usaldamast: "when exiting onboarding mode, directly evaluate whether cursor is over a UI region and make the correct IPC call." Kui sa vahetad režiime, ei oota sa, et järgmine liigutus ütleks, kus kursor on — sa vaatad.
Ja tee loovutus tagasipööratavaks. Peen risk kogu selles on see, et ülekate loovutab nüüd ajutiselt oma z-järjekorra ja klõpsupüüdmise, ja miski peab need tagasi andma. Kui taastamiskutse viskab vea — IPC luksatus, päringu keskel maha võetud aken — võib rakendus jääda kinni nii, et isRequestingPermission on jäänud true peale ja tume taust peidab su kogu ekraani jäädavalt. Nii et väljumistee mähiti nii, et seisundi lähtestamine "käivitub alati," pluss mitmeminutiline turvataimer ja puhastus akna hävitamisel. Reegel, mis sellest tuleneb: iga kord, kui ülekate loovutab sisendi, peab loovutuse tagasivõtmine olema finally-s, mitte õnnelikul teel.
v1.8.14: kui püüdmise pool puruneb
Lepingu teine pool on "võta sisendit seal, kus on UI." v1.8.14 näitab, kuidas see välja näeb, kui ülekate on liiga haarav — kui sellega suhtlemine maksab sulle selle äpi sisendifookuse, mis sulle tegelikult korda läheb.
Muudatuste logis on sümptom fookusevargus, ja siin on aus versioon, sest git-ajalugu on õpetlikum kui väljalaskemärkus. Ülekatte otseteekäsitlejad — nähtavuse lülitamine Cmd+B-ga, vestlusakna avamine, selle kerimine — kutsusid igaüks varem mainWindow.focus() ja macOS-il app.focus({ steal: true }), et ülekate oleks esiplaanil, kui sa selle vallandad. Probleem on selles, et ülekatte fokuseerimine deaktiveerib selle, mis oli enne esiplaanil — su koosoleku, su redaktori — nii et neile mõeldud klahvivajutus võis maanduda eikuhugi või valesse kohta. Ülekate haaras asja, mida tal tegelikult vaja polnud.
See viimane osa on kogu õppetund, ja meeskond leidis selle raske tee kaudu. Harust, millest sai v1.8.14, üritati esmalt lahendada just lohistamist — sest lohistamisel on oma kaunis versioon sellest veast: kui sa lohistad täisekraani ülekatet, liigub aken paigalseisva kursori all, nii et hõljumismudel arvab, et kursor "lahkus" juhtnupult, ja pöördub lohistamise keskel klikiläbivaks, kukutades lohistuse. Nii et nad lisasid dragLock-i. Siis üritasid nad kogu akent mittefokuseeritavaks teha — mis lõhkus kirjutamise ja lohistamise, nii et nad taastasid fokuseeritavuse, siis kitsendasid mittefokuseeritavuse ainult tekstisisenditele, siis kiskusid lüliti välja. Nad ehitasid omadraagi IPC, et asendada Electroni natiivne lohistamine. Nad lisasid hägustamise-ootamatul-fookusel. Nädal seda.
Siis kustutasid nad peaaegu kõik. Commit, mis tarniti, keeras tagasi omadraagi, fokuseeritavuse lüliti ja hägustamiskäsitleja ning jättis alles täpselt ühe idee: lõpeta focus() kutsumine. Taipamine, kirjutatud koodikommentaari, on see, et ülekate ei vajanud OS-i fookust kunagi üldse — "IPC messages (webContents.send) work without window focus." Ülekate juhib oma renderdajat otse; fookus oli puhas kaasnev kahju. Tarnitav parandus polnud nutikas ühe päevaga ehitatud omadraagi ümberkirjutus. See oli nende fookusekutsete lahutamine, mida seal poleks pidanud olema. (Väärib otse ütlemist: muudatuste logi ütleb "while dragging it," aga see, mis tegelikult tarniti, oli fookusevarguse eemaldamine klaviatuuriotsete käsitlejatest — lohistamisspetsiifiline masinavärk uuriti läbi ja keerati sama nädala jooksul tagasi.)
Panused, sõna-sõnalt
Kui tahad tõestust, et see leping pole akadeemiline, vaata, mis juhtus kuid hiljem, kui ülekate jäi selle valele poolele kinni. Palju hilisemas väljalaskes jooksis renderdusprotsess kokku, kui ülekate oli püüdmisrežiimis — setIgnoreMouseEvents(false), aken haarab kõik klõpsud — ja ilma elava UI-ta, mis selle vabastaks, istus nähtamatu kogu-töölaua aken seal, süües iga klõpsu kasutaja masinal, kuni ta sundsulges. Läbipaistev aken, mis on püüdmisrežiimis kinni, pole surnud nupp; see on kogu töölauda hõlmav klõpsulõks. (Sel oli oma diagnoos — vaata kui su AI-märkmetegija lõpetab koosoleku keskel salvestamise, mis paneb selle kokku hoopis teistsuguse veaga samas väljalaskes.) See on kogu probleemi teravaim sõnastus: jäta lüliti ühtpidi kinni ja su UI on surnud; teistpidi kinni ja su kogu töölaud on.
Kolm asja, mida need väljalasked meile õpetasid
- Klikiläbiv ülekate on kahepoolne leping, ja mõlemad pooled ebaõnnestuvad erinevalt. Liiga läbipaistev ja su enda juhtnupud lähevad surnuks; liiga haarav ja sa neelad teiste äppide sisendit. Enamik ülekatte vigu on lihtsalt üks neist kahest, ja selle nimetamine, kummal poolel sa oled, ütleb, kust otsida.
- Kõrgeim alati-pealmine tase pole auhind. Kõige kohal istumine tähendab istumist nende süsteemidialoogide kohal, mida sul on vaja, et kasutaja näeks. Kavanda selleks, millal su ülekate peaks viisakalt tagasi astuma — ja tee tagasi üles astumine
finally-s tagasipööratavaks, sest poolik loovutus on halvem kui loovutamata jätmine. - Lahutamine võidab nutikuse. Võidukas v1.8.14 parandus oli
focus()kutsete kustutamine, mida ülekatel kunagi vaja polnud, leitud alles pärast nädalast omadraagi käsitluse ehitamist ja tagasikeeramist. Kui funktsioon aina platvormiga võitleb, on esimene küsimus, kas saad eemaldada selle asja, mis võitleb.
Eelmise peatüki jaoks v1 loos — väljalaske viimine CI-sse, kaks korda (v1.8.4); ja kogu kaare jaoks tarkvara täiuslikkuseni tarnimise anatoomia.