Steven
Steven7 min lukuaika

Läpiklikattavan peittoikkunan kaksi vikatilaa

GeekByen ikkuna leijuu kaiken päällä ja päästää klikkauksesi lävitseen — paitsi siellä missä sillä on painikkeita. Se on kaksipuolinen sopimus, ja v1.8.5 ja v1.8.14 näyttävät miltä näyttää kun kumpikin puoli hajoaa: yksi julkaisu jossa peittoikkuna nielaisi järjestelmädialogin, yksi jossa se varasti näppäinpainalluksesi. Toisen voittava korjaus oli koodin poistaminen.

Ohjelmistokehitys
Electron
Desktop
GeekBye-julkaisut
Läpiklikattavan peittoikkunan kaksi vikatilaa

GeekByen pääikkuna ei ole tavallinen ikkuna. Se on reunukseton, läpinäkyvä ja aina päällimmäisenä — lasilevy joka leijuu kaiken tekemäsi päällä, muutama kontrolli piirrettynä siihen. Kiinnostava insinöörityö ei ole se että se leijuu; se on se että hiiresi on tarkoitus mennä suoraan lasin läpi kaikkialla paitsi kontrollien kohdalla. Klikkaa peittoikkunan painiketta ja peittoikkuna saa klikkauksen. Klikkaa sen vieressä olevaa tyhjää tilaa ja klikkaus laskeutuu alla olevaan kokoussovellukseesi, aivan kuin peittoikkunaa ei olisi siellä lainkaan.

Se on sopimus jolla on kaksi puolta: vastaanota syöte täsmälleen siellä missä on käyttöliittymää, ja ole täysin näkymätön syötteelle kaikkialla muualla. Molempien puolten on pidettävä joka hetki, ja jokainen bugi tässä sovelluksen osassa on jommankumman puolen rikkomus. Kaksi julkaisua, kahden kuukauden välein, ovat puhtain mahdollinen havainnollistus — koska kumpikin rikkoi eri puolen.

Sopimus, mekaanisesti

Toteutus on hook, useMousePassthrough, ja se on pieni. Ikkuna on oletuksena läpiklikattava. Globaali mousemove-kuuntelija esittää jokaisella liikkeellä yhden kysymyksen: onko kursori kontrollin päällä? Kontrollit on merkitty data-ui-region-attribuutilla, joten testi on kirjaimellisesti target.closest('[data-ui-region]'). Kontrollin päällä se kutsuu setIgnoreMouseEvents(false) ja ikkuna nappaa klikkaukset. Läpinäkyvän tilan päällä se kutsuu setIgnoreMouseEvents(true, { forward: true }) ja klikkaukset putoavat läpi.

Tuo forward: true on kantava yksityiskohta. Kun ikkuna kerran jättää hiiritapahtumat huomiotta, se yleensä lakkaa vastaanottamasta niitä kokonaan — mukaan lukien mousemove-tapahtumat joita hook tarvitsee huomatakseen kursorin tulevan takaisin kontrollin päälle. forward: true käskee Electronia välittämään liikkeet huomiotta jättävään ikkunaan silti, jotta se voi virittää itsensä uudelleen. Ilman sitä peittoikkuna menisi läpiklikattavaksi kerran eikä palaisi koskaan. Hook myös kutsuu IPC-rajan yli vain tilan siirtymissä — kursorin ylittäessä rajan käyttöliittymän ja lasin välillä — ei jokaisella liikkeen pikselillä. Muista tuo optimointi; siinä on reikä, ja v1.8.5 putosi siihen.

v1.8.5: kun läpinäkyvyyspuoli hajoaa

Sopimuksen läpinäkyvyyspuoli on "ole näkymätön syötteelle kaikkialla missä ei ole käyttöliittymää." v1.8.5 näyttää miltä näyttää kun peittoikkuna on liian läsnä — kun se nappaa tai estää syötettä jonka olisi pitänyt antaa mennä ohi. Laukaisija oli macOS:n käyttöoikeusdialogit, ja siinä oli kaksi erillistä vikaa pinottuna päällekkäin.

Peittoikkuna oli fyysisesti dialogin yläpuolella. Aina päällimmäisenä olevalla ikkunalla on taso, ja GeekByen peittoikkuna istui korkeimmalla jonka macOS tarjoaa — screen-saver. Se on lähes kaiken yläpuolella, mukaan lukien järjestelmän omat Microphone- ja Screen Recording -käyttöoikeusdialogit. Niinpä onboardingin aikana, juuri sillä hetkellä kun macOS pyysi lupaa, dialogi saattoi renderöityä peittoikkunan alle ja olla näkymätön. Korjaus, setPermissionMode, on vain macOS:lle ja tekee epäintuitiivisen asian: se laskee peittoikkunan käyttöoikeuspyynnön ajaksi floating-tasolle ja palauttaa sen jälkeenpäin. Ja sen palauttaminen vaati tietyn tempun, dokumentoituna suoraan committiin: macOS "doesn't always visually re-raise a window when setting the same level it was previously at," joten koodi askeltaa ikkunan välitason kautta — floating → status → screen-saver — pakottaakseen ikkunapalvelimen laskemaan pinoamisjärjestyksen uudelleen. Et aseta haluamaasi tasoa; kävelet siihen.

Peittoikkuna söi dialogin klikkaukset. Onboarding piirtää tumman koko ikkunan taustan, ja läpäisy-hook käsittelee sen erikoistapauksena: niin kauan kuin tuo tausta on ylhäällä, se pakottaa klikkausnappauksen koko ikkunaan jotta velho on täysin vuorovaikutteinen. Mutta "nappaa jokainen klikkaus" on juuri väärin kun järjestelmädialogi yrittää kerätä yhden. Korjaus riisuu taustan merkkiattribuutin käyttöoikeuspyynnön ajaksi, joten hook putoaa nappaustilasta ja dialogin painikkeet saavat klikkauksensa. Peittoikkuna pitää omat kontrollinsa vuorovaikutteisina; se vain lakkaa teeskentelemästä että tyhjä lasi on painike.

Reikä optimoinnissa. Muista että hook toimii vain siirtymissä. Kun ikkuna poistui nappaustilasta, se ajoi leijuntatestin uudelleen — mutta jos kursorisi jo istui kontrollin päällä sillä hetkellä, vastaus kysymykseen "olemmeko käyttöliittymän päällä?" oli jo "kyllä," joten mitään siirtymää ei laukennut, eikä läpiklikkausta koskaan poistettu käytöstä. Kontrolli näytti elävältä ja oli kuollut. Korjaus (1b6eb20, lainattuna) lakkaa luottamasta siirtymään: "when exiting onboarding mode, directly evaluate whether cursor is over a UI region and make the correct IPC call." Kun vaihdat tiloja, et odota seuraavan liikkeen kertovan missä kursori on — katsot.

Ja tee luovutuksesta peruutettava. Hienovarainen riski kaikessa tässä on että peittoikkuna nyt tilapäisesti luovuttaa z-järjestyksensä ja klikkausnappauksensa, ja jonkin on annettava ne takaisin. Jos palautuskutsu heittää — IPC-nikahdus, pyynnön keskellä puretuksi tullut ikkuna — sovellus voisi jäädä jumiin niin että isRequestingPermission on juuttunut arvoon true ja tumma tausta piilottaa koko näyttösi pysyvästi. Niinpä poistumispolku kiedottiin niin että tilan nollaus "suoritetaan aina," plus monen minuutin turva-aikakatkaisu ja siivous ikkunan tuhoutuessa. Sääntö joka siitä seuraa: joka kerta kun peittoikkuna luovuttaa syötteen, luovutuksen peruminen on oltava finally-lohkossa, ei onnellisella polulla.

v1.8.14: kun nappauspuoli hajoaa

Sopimuksen toinen puoli on "vastaanota syöte siellä missä on käyttöliittymää." v1.8.14 näyttää miltä näyttää kun peittoikkuna on liian ahne — kun sen kanssa vuorovaikuttaminen maksaa sinulle sen sovelluksen syötefokuksen josta oikeasti välität.

Muutoslokissa oire on fokuksen varastaminen, ja tässä on rehellinen versio, koska git-historia on opettavaisempi kuin julkaisumuistiinpano. Peittoikkunan pikanäppäinkäsittelijät — näkyvyyden vaihto Cmd+B:llä, chat-ikkunan avaus, sen vieritys — kutsuivat kukin ennen mainWindow.focus(), ja macOS:llä app.focus({ steal: true }), varmistaakseen että peittoikkuna oli etummaisena kun sen laukaisit. Ongelma on että peittoikkunan fokusointi deaktivoi sen mikä oli etummaisena ennen — kokouksesi, editorisi — joten niille tarkoitettu näppäinpainallus saattoi laskeutua ei-mihinkään tai väärään paikkaan. Peittoikkuna nappasi asian jota se ei oikeasti tarvinnut.

Tuo viimeinen osa on koko oppitunti, ja tiimi löysi sen kantapään kautta. Haara josta tuli v1.8.14 yritti ensin ratkaista nimenomaan vetämistä — koska vetämisellä on oma kaunis versionsa tästä bugista: kun vedät koko näytön peittoikkunaa, ikkuna liikkuu paikallaan olevan kursorin alla, joten leijuntamalli luulee että osoitin "lähti" kontrollilta ja kääntyy läpiklikattavaksi kesken vedon, pudottaen vedon. Niinpä he lisäsivät dragLockin. Sitten he yrittivät tehdä koko ikkunasta ei-fokusoitavan — mikä rikkoi kirjoittamisen ja vetämisen, joten he palauttivat fokusoitavuuden, sitten kavensivat ei-fokusoitavuuden pelkkiin tekstikenttiin, sitten repivät kytkennän ulos. He rakensivat oman veto-IPC:n korvaamaan Electronin natiivin vedon. He lisäsivät sumennuksen-odottamattomalla-fokuksella. Viikko tätä.

Sitten he poistivat siitä lähes kaiken. Toimitettu commit peruutti oman vedon, fokusoitavuuskytkennän ja sumennuskäsittelijän, ja säilytti täsmälleen yhden idean: lakkaa kutsumasta focus(). Oivallus, kirjoitettuna koodikommenttiin, on että peittoikkuna ei koskaan alun perin tarvinnut käyttöjärjestelmän fokusta — "IPC messages (webContents.send) work without window focus." Peittoikkuna ohjaa omaa renderöijäänsä suoraan; fokus oli puhdasta oheisvahinkoa. Toimitettava korjaus ei ollut nokkela päivässä rakennettu oman vedon uudelleenkirjoitus. Se oli niiden fokuskutsujen vähentäminen joita siellä ei olisi pitänyt olla. (Arvoista sanoa suoraan: muutosloki sanoo "while dragging it," mutta se mikä oikeasti toimitettiin oli fokuksen varastamisen poistaminen näppäinpikakäsittelijöistä — vetokohtainen koneisto tutkittiin ja peruutettiin samalla viikolla.)

Panokset, kirjaimellisesti

Jos haluat todisteen että tämä sopimus ei ole akateeminen, katso mitä tapahtui kuukausia myöhemmin kun peittoikkuna jäi jumiin sen väärälle puolelle. Paljon myöhemmässä julkaisussa renderöintiprosessi kaatui peittoikkunan ollessa nappaustilassa — setIgnoreMouseEvents(false), ikkuna nappaa kaikki klikkaukset — ja ilman elävää käyttöliittymää vapauttamaan sitä, näkymätön koko työpöydän ikkuna istui siinä syöden jokaisen klikkauksen käyttäjän koneella kunnes hän pakotti sen sulkeutumaan. Läpinäkyvä ikkuna joka on jumissa nappaustilassa ei ole kuollut painike; se on koko työpöydän laajuinen klikkausansa. (Sillä oli oma diagnoosinsa — katso kun AI-muistiinpanijasi lakkaa tallentamasta kesken kokouksen, joka yhdistää sen hyvin erilaiseen bugiin samassa julkaisussa.) Se on koko ongelman terävin ilmaus: jätä kytkin jumiin toiseen suuntaan ja käyttöliittymäsi on kuollut; jumissa toiseen suuntaan ja koko työpöytäsi on.

Kolme asiaa jotka nämä julkaisut opettivat meille

  1. Läpiklikattava peittoikkuna on kaksipuolinen sopimus, ja molemmat puolet pettävät eri tavalla. Liian läpinäkyvä ja omat kontrollisi menevät kuolleiksi; liian ahne ja nielaiset muiden sovellusten syötteen. Useimmat peittoikkunabugit ovat vain jompikumpi näistä kahdesta, ja sen nimeäminen kummalla puolella olet kertoo mistä katsoa.
  2. Korkein aina-päällä-taso ei ole palkinto. Kaiken yläpuolella istuminen tarkoittaa istumista niiden järjestelmädialogien yläpuolella jotka tarvitset käyttäjän näkevän. Suunnittele sitä varten milloin peittoikkunasi pitäisi kohteliaasti astua alas — ja tee takaisin ylös astuminen peruutettavaksi finally-lohkossa, koska puolittainen luovutus on pahempi kuin luovuttamatta jättäminen.
  3. Vähentäminen voittaa nokkeluuden. Voittava v1.8.14-korjaus oli focus()-kutsujen poistaminen joita peittoikkuna ei koskaan tarvinnut, löydetty vasta viikon oman vetokäsittelyn rakentamisen ja peruuttamisen jälkeen. Kun ominaisuus jatkaa taistelua alustaa vastaan, ensimmäinen kysymys on voitko poistaa sen asian joka taistelee.

Edellinen luku v1-tarinassa: julkaisun siirtäminen CI:hin, kahdesti (v1.8.4); ja koko kaarelle, ohjelmiston toimittamisen anatomia täydellisyyteen asti.