
Klikšķcaurlaidīga pārklājuma divi atteices režīmi
GeekBye logs peld pāri visam un ļauj taviem klikšķiem iziet cauri — izņemot tur, kur tam ir pogas. Tas ir divpusējs līgums, un v1.8.5 un v1.8.14 rāda, kā izskatās, kad salūst katra puse: viens laidiens, kur pārklājums aprija sistēmas dialogu, un viens, kur tas nozaga tavus taustiņnospiedumus. Uzvarošais otrā labojums bija koda dzēšana.
GeekBye galvenais logs nav parasts logs. Tas ir bezrāmja, caurspīdīgs un vienmēr virspusē — stikla plāksne, kas peld pāri visam, ko tu dari, ar dažām uz tās uzzīmētām vadīklām. Interesantā inženierija nav tas, ka tas peld; tas ir tas, ka tavai pelei ir jā_iziet taisni cauri stiklam_ visur, izņemot vadīklas. Noklikšķini pogu uz pārklājuma, un pārklājums saņem klikšķi. Noklikšķini tukšo vietu tam blakus, un klikšķis nokļūst tavā sanāksmes lietotnē apakšā, it kā pārklājuma tur nemaz nebūtu.
Tas ir līgums ar divām pusēm: saņem ievadi tieši tur, kur ir UI, un esi pilnīgi neredzams ievadei visur citur. Abām pusēm ir jātur katrā mirklī, un katra kļūda šajā lietotnes daļā ir vienas vai otras puses pārkāpums. Divi laidieni, ar divu mēnešu starpību, ir vistīrākais iespējamais piemērs — jo katrs no tiem salauza citu pusi.
Līgums, mehāniski
Īstenojums ir āķis, useMousePassthrough, un tas ir mazs. Logs pēc noklusējuma ir klikšķcaurlaidīgs. Globāls mousemove klausītājs katrā kustībā uzdod vienu jautājumu: vai kursors ir virs vadīklas? Vadīklas ir iezīmētas ar data-ui-region atribūtu, tāpēc tests burtiski ir target.closest('[data-ui-region]'). Virs vadīklas tas izsauc setIgnoreMouseEvents(false), un logs tver klikšķus. Virs caurspīdīgas telpas tas izsauc setIgnoreMouseEvents(true, { forward: true }), un klikšķi izkrīt cauri.
Šis forward: true ir nesošā detaļa. Tiklīdz logs ignorē peles notikumus, tas parasti pārstāj tos saņemt pavisam — ieskaitot mousemove notikumus, kas āķim vajadzīgi, lai pamanītu kursoru nākam atpakaļ virs vadīklas. forward: true liek Electron tik un tā turpināt pārsūtīt kustības ignorējošajam logam, lai tas varētu sevi no jauna sagatavot. Bez tā pārklājums vienreiz kļūtu klikšķcaurlaidīgs un nekad neatgrieztos. Āķis arī izsauc pāri IPC robežai tikai stāvokļa pārejās — kursoram šķērsojot robežu starp UI un stiklu — nevis katrā kustības pikselī. Atceries šo optimizāciju; tai ir caurums, un v1.8.5 tajā iekrita.
v1.8.5: kad salūst caurspīdīguma puse
Līguma caurspīdīguma puse ir "esi neredzams ievadei visur, kur nav UI." v1.8.5 rāda, kā izskatās, kad pārklājums ir pārāk klātesošs — kad tas tver vai bloķē ievadi, kurai vajadzēja ļaut paiet garām. Iemesls bija macOS atļauju dialogi, un bija divas atsevišķas atteices, saliktas cita virs citas.
Pārklājums fiziski atradās virs dialoga. Vienmēr virspusē esošam logam ir līmenis, un GeekBye pārklājums atradās augstākajā, ko macOS piedāvā — screen-saver. Tas ir virs gandrīz visa, ieskaitot pašas sistēmas Microphone un Screen Recording atļauju dialogus. Tātad onboarding laikā, tieši tajā brīdī, kad macOS prasīja atļauju, dialogs varēja tikt attēlots zem pārklājuma un būt neredzams. Labojums, setPermissionMode, ir tikai macOS un dara neintuitīvo: tas atļaujas pieprasījuma laikā pazemina pārklājumu līdz floating līmenim, pēc tam to atjauno. Un tā atjaunošanai bija vajadzīgs īpašs triks, dokumentēts tieši komitā: macOS "doesn't always visually re-raise a window when setting the same level it was previously at," tāpēc kods soļo logu cauri starplīmenim — floating → status → screen-saver — lai piespiestu logu serveri pārrēķināt sakraušanas secību. Tu neiestati vēlamo līmeni; tu aizej līdz tam.
Pārklājums ēda dialoga klikšķus. Onboarding uzzīmē tumšu pilnloga fonu, un caurlaides āķis to apstrādā īpaši: kamēr šis fons ir augšā, tas piespiež klikšķu tveršanu visam logam, lai vednis būtu pilnībā lietojams. Bet "tvert katru klikšķi" ir tieši nepareizi, kad sistēmas dialogs mēģina kādu savākt. Labojums noņem fona marķiera atribūtu atļaujas pieprasījuma laikā, tāpēc āķis izkrīt no tveršanas režīma, un dialoga pogas saņem savus klikšķus. Pārklājums saglabā savas vadīklas lietojamas; tas vienkārši pārstāj izlikties, ka tukšais stikls ir poga.
Caurums optimizācijā. Atceries, ka āķis darbojas tikai pārejās. Kad logs izgāja no tveršanas režīma, tas no jauna palaida novietojuma testu — bet ja tavs kursors tajā brīdī jau atradās uz vadīklas, atbilde uz "vai esam virs UI?" jau bija "jā," tāpēc neviena pāreja neizpaudās, un klikšķcaurlaidība nekad netika atspējota. Vadīkla izskatījās dzīva un bija mirusi. Labojums (1b6eb20, citēts) pārstāj uzticēties pārejai: "when exiting onboarding mode, directly evaluate whether cursor is over a UI region and make the correct IPC call." Kad tu maini režīmus, tu negaidi, kamēr nākamā kustība pateiks, kur kursors atrodas — tu paskaties.
Un padari atdošanu atgriezenisku. Smalkais risks visā šajā ir tas, ka pārklājums tagad uz laiku atdod savu z-secību un savu klikšķu tveršanu, un kaut kam tie ir jāatdod atpakaļ. Ja atjaunošanas izsaukums met kļūdu — IPC ķeza, pieprasījuma vidū nojaukts logs — lietotne varētu iestrēgt ar isRequestingPermission, kas iesprūdis kā true, un tumšu fonu, kas pastāvīgi paslēpj visu tavu ekrānu. Tāpēc iziešanas ceļš tika ietīts tā, lai stāvokļa atiestate "vienmēr izpildās," plus daudzu minūšu drošības taimauts un tīrīšana loga iznīcināšanas brīdī. Noteikums, kas no tā izriet: katru reizi, kad pārklājums atdod ievadi, atdošanas atsaukšanai jābūt finally, nevis laimīgajā ceļā.
v1.8.14: kad salūst tveršanas puse
Otra līguma puse ir "saņem ievadi tur, kur ir UI." v1.8.14 rāda, kā izskatās, kad pārklājums ir pārāk mantkārīgs — kad mijiedarbība ar to maksā tev tās lietotnes ievades fokusu, kura tev patiešām rūp.
Izmaiņu žurnālā simptoms ir fokusa zagšana, un šeit ir godīgā versija, jo git vēsture ir pamācošāka nekā laidiena piezīme. Pārklājuma īsceļu apstrādātāji — redzamības pārslēgšana ar Cmd+B, tērzēšanas loga atvēršana, tā ritināšana — katrs mēdza izsaukt mainWindow.focus(), un macOS app.focus({ steal: true }), lai pārliecinātos, ka pārklājums ir priekšplānā, kad tu to iedarbini. Problēma ir tā, ka pārklājuma fokusēšana deaktivizē to, kas bija priekšplānā iepriekš — tavu sanāksmi, tavu redaktoru — tāpēc taustiņnospiedums, kas domāts tiem, varēja nokļūt nekur vai nepareizā vietā. Pārklājums ķēra lietu, kas tam patiesībā nebija vajadzīga.
Šī pēdējā daļa ir visa mācība, un komanda to atrada grūtajā ceļā. Zars, kas kļuva par v1.8.14, vispirms mēģināja atrisināt tieši vilkšanu — jo vilkšanai ir sava skaistā šīs kļūdas versija: kad tu velc pilnekrāna pārklājumu, logs pārvietojas zem nekustīga kursora, tāpēc novietojuma modelis domā, ka rādītājs "atstāja" vadīklu, un pārslēdzas uz klikšķcaurlaidību vilkšanas vidū, nometot vilkšanu. Tāpēc viņi pievienoja dragLock. Tad viņi mēģināja padarīt visu logu nefokusējamu — kas salauza rakstīšanu un vilkšanu, tāpēc viņi atjaunoja fokusējamību, tad sašaurināja nefokusējamību tikai līdz teksta ievadēm, tad izrāva pārslēgu ārā. Viņi izveidoja pielāgotu vilkšanas IPC, lai aizstātu Electron vietējo vilkšanu. Viņi pievienoja izplūšanu-negaidīta-fokusa-gadījumā. Vesela nedēļa šā.
Tad viņi izdzēsa gandrīz visu. Komits, kas nonāca laidienā, atcēla pielāgoto vilkšanu, fokusējamības pārslēgu un izplūšanas apstrādātāju un saglabāja tieši vienu ideju: beidz izsaukt focus(). Atklāsme, ierakstīta koda komentārā, ir tāda, ka pārklājumam OS fokuss nekad no paša sākuma nebija vajadzīgs — "IPC messages (webContents.send) work without window focus." Pārklājums tieši vada savu renderētāju; fokuss bija tīri blakusbojājumi. Piegādājamais labojums nebija gudrā vienā dienā uzbūvētā pielāgotās vilkšanas pārrakstīšana. Tā bija fokusa izsaukumu atņemšana, kuriem tur nevajadzēja būt. (Vērts pateikt tieši: izmaiņu žurnāls saka "while dragging it," bet tas, kas patiešām nonāca laidienā, bija fokusa zagšanas noņemšana no tastatūras īsceļu apstrādātājiem — vilkšanai specifiskā mehānika tika izpētīta un atcelta tajā pašā nedēļā.)
Likmes, burtiski
Ja gribi pierādījumu, ka šis līgums nav akadēmisks, paskaties, kas notika mēnešus vēlāk, kad pārklājums iestrēga tā nepareizajā pusē. Daudz vēlākā laidienā renderētāja process avarēja, kamēr pārklājums bija tveršanas režīmā — setIgnoreMouseEvents(false), logs ķer visus klikšķus — un bez dzīva UI, kas to atbrīvotu, neredzamais pilndarbvirsmas logs sēdēja tur, ēdot katru klikšķi lietotāja datorā, līdz viņš piespiedu kārtā aizvēra. Caurspīdīgs logs, iestrēdzis tveršanas režīmā, nav mirusi poga; tas ir visu darbvirsmu aptverošs klikšķu slazds. (Tam bija sava diagnoze — skat. kad tavs AI pierakstītājs pārstāj ierakstīt sanāksmes vidū, kur tas savienots ar pavisam citu kļūdu tajā pašā laidienā.) Tas ir viskrasākais visas problēmas formulējums: iestrēdzini pārslēgu vienā virzienā, un tavs UI ir miris; iestrēdzini otrā, un tāda ir visa tava darbvirsma.
Trīs lietas, ko šie laidieni mums iemācīja
- Klikšķcaurlaidīgs pārklājums ir divpusējs līgums, un abas puses atsakās atšķirīgi. Pārāk caurspīdīgs, un tavas paša vadīklas kļūst mirušas; pārāk mantkārīgs, un tu aprij citu lietotņu ievadi. Vairums pārklājuma kļūdu ir tikai viena no šīm divām, un tas, kuru pusi nosauc, pasaka, kur meklēt.
- Augstākais vienmēr-virspusē līmenis nav balva. Sēdēt virs visa nozīmē sēdēt virs sistēmas dialogiem, kurus tev vajag, lai lietotājs redzētu. Projektē tā, lai tavs pārklājums pieklājīgi kāptu lejā — un padari kāpšanu atpakaļ augšā atgriezenisku
finally, jo pa pusei pabeigta atdošana ir sliktāka nekā neatdošana vispār. - Atņemšana pārspēj gudrību. Uzvarošais v1.8.14 labojums bija
focus()izsaukumu dzēšana, kuri pārklājumam nekad nebija vajadzīgi, atrasti tikai pēc nedēļas ilgas pielāgotas vilkšanas apstrādes būvēšanas un atcelšanas. Kad funkcija turpina cīnīties ar platformu, pirmais jautājums ir, vai vari noņemt to, kas cīnās.
Iepriekšējai nodaļai v1 stāstā — laidiena ievietošana CI, divreiz (v1.8.4); un visai lokai — programmatūras piegādes līdz pilnībai anatomiju.