← bitàcola

v0.2.0 — pestanyes, i després grups

Una pestanya és un motor sencer

A rumb no hi ha cap representació lleugera d'una pestanya. Una pestanya és propietària d'un context de render fora de pantalla derivat del pare headless compartit, d'un WebView de Servo construït a dins i del delegate que el vigila. Deu pestanyes són deu WebViews, deu fils de script, deu heaps de JS, deu framebuffers.

tick() crida spin_event_loop un sol cop i això mou totes les pestanyes — les de fons continuen carregant —, però només la pestanya activa pinta i exporta un dma-buf a la casella compartida. El compositor mai no veu més d'un framebuffer. set_active treu la limitació i mostra la pestanya que rep el focus, i limita i amaga totes les altres que estiguin vives. Això és el que impedeix que una pestanya de fons pinti per sempre: si no, un cursor que parpelleja en una pàgina que has deixat enrere es repintarà a ple ritme, i mentre ho fa, perdrà memòria.

El context de render per pestanya també va resoldre un problema més antic. Ara redimensionar intercanvia l'FBO fora de pantalla en comptes de redimensionar la superfície de surfman — que és el que provocava el panic (servo/servo#38369) i el motiu pel qual l'ítem del redimensionament de finestra portava al backlog des del maig. Ara el bucle de render pot fer coincidir la mida de render de Servo amb l'àrea de contingut a cada fotograma, de manera que les media queries de CSS s'avaluen contra el viewport de debò.

Tornar la memòria

Una pestanya que porta prou estona inactiva es descarta: alliberar-ne el WebView envia CloseWebView a la constellation i s'endú el fil de script, el heap de JS, el DOM i la maquetació. El context de render es conserva, així que despertar-la és reconstruir sobre el mateix FBO i no pas remenar textures de GL noves. La URL, el títol, el favicon i la pertinença al grup sobreviuen; despertar-la torna a carregar des de la URL desada.

A Linux no n'hi ha prou d'alliberar-lo. El desmuntatge allibera la memòria dins del procés, però glibc se la queda en reserva i l'RSS no baixa mai. Per això cada cinc segons el bucle de render crida malloc_trim(0) i, en la mateixa passada, registra l'RSS de /proc/self/statm al costat del nombre de pestanyes i de la URL activa. El comentari que hi ha sobre aquest vigilant en diu «l'incident dels 24 GB».

Les proteccions són on va anar a parar la cura. Mai la pestanya activa, mai una de fixada, mai una que ja dorm; després, que porti inactiva més del llindar, que no hi hagi hagut cap canvi de títol ni de favicon dins d'una finestra de senyal — com a mínim cinc segons, fins i tot en mode instantani, perquè una pestanya que encara carrega amb el títol parpellejant no es mati a si mateixa — i que no estigui reproduint contingut multimèdia. Passat això, s'executa dins de la pàgina una sonda de JavaScript de només lectura: cert si hi ha un element de vídeo o d'àudio reproduint-se, si un camp editable conté un valor diferent del predeterminat, o si window.onbeforeunload és una funció. Res de fer monkey-patching de fetch ni de WebSocket, que correria el risc de trencar el web que està inspeccionant; qualsevol error o resposta que no sigui booleana compta com a ocupat, de manera que no es descarta res que no s'hagi pogut inspeccionar.

La sonda existeix perquè l'API per a qui incrusta no li dona res: Servo no exposa ni fetch o XHR en curs, ni un WebSocket o un flux SSE oberts, ni formularis modificats, ni un beforeunload registrat. La sonda recupera les dues últimes coses des de dins de la pàgina i les tres primeres no les veu de cap manera — una pestanya que manté un WebSocket obert, callada i inactiva, encara pot ser suspesa. El valor predeterminat de quinze minuts ho fa improbable, no impossible, i el comentari de documentació ho diu.

active drawn every frame background throttled, hidden discarded title and URL only switch away select idle long enough WebView and framebuffer dropped waking reloads the page — the state inside it is gone playing audio, or holding input nobody submitted, blocks this edge

Els grups són un problema de disposició

La pertinença a un grup és un camp de la pestanya: un id de grup opcional. El nom, el color i l'estat de plegat viuen al shell, indexats per aquest id. Tota la resta dels grups és renderitzat.

La barra recorre les pestanyes en ordre. Una pestanya sense grup es dibuixa com una fitxa solta; el primer membre d'un grup fa que tot el grup es dibuixi en aquella posició — la píndola i, després, la fitxa de cada membre — i els membres següents se salten. Un grup sembla continu perquè moure'n un ho mou tot: move_group va traient els membres començant per l'índex més alt, perquè els índexs baixos continuïn sent vàlids, els restaura en el seu ordre i els torna a inserir com un sol tram entre els que no en són membres. Esbrinar on ha acabat la pestanya activa després d'això es guanya un paràgraf propi al codi font.

El tractament visual va passar per unes quantes formes en un sol matí: un punt de color per fitxa, després una vora inferior de color a cada fitxa agrupada perquè els membres contigus formessin una línia contínua, i finalment una sola línia dibuixada per a tot el bloc. Un subratllat horitzontal no té cap sentit en una barra vertical, així que allà va passar a ser un rail que baixa per l'esquerra de la columna.

Una de les passades es va equivocar en tots dos sentits. Un grup plegat va rebre una insígnia que mostrava quantes pestanyes amagava; quaranta minuts després, la insígnia es va esborrar del tot. Ara un grup plegat és només el seu nom, i amaga tots els membres.

Sis intents amb el cursor d'inserció

Arrossegar i deixar anar va costar més commits que el model de grups al qual serveix. La primera forma omplia amb un ressaltat la destinació que tenies sota el punter, cosa que t'indica sobre quina pestanya ets — que no és la mateixa pregunta que on anirà a parar la pestanya arrossegada. Va passar a ser una línia d'accent de dos píxels a la vora d'entrada de la fitxa que tens sota el punter, l'esquerra en una barra horitzontal i la de dalt en una de vertical, que vol dir: aquí és on s'inserirà.

Fer que el cursor fos precís va destapar un bug que el ressaltat ple havia amagat. move_tab inseria directament a l'índex de destinació, però treure primer la pestanya arrossegada desplaça un lloc avall tots els índexs posteriors, així que un moviment cap a la dreta queia una casella tard i reordenar només funcionava cap a l'esquerra. L'arranjament són tres línies i el comentari que l'explica és més llarg que el codi.

El final de la barra va costar tres intents. Una zona de deixada al darrere, després del botó +, funcionava però allunyava el + de l'última pestanya i deixava que el cursor d'inserció aparegués més enllà; encongir-la i posar-la abans del + va arreglar el cursor i va deixar el forat. La resposta va ser esborrar la zona i fer que el mateix + fos la destinació d'«inserir al final».

THE STRIP tab pill an expanded group + before before the group inside it at the end a cursor, not a highlighted zone — the reader is told exactly where it lands

L'últim forat era el començament de la barra: si un grup ocupa la posició zero, no hi ha cap fitxa on deixar anar abans d'ell, perquè deixar-la anar sobre la primera pestanya del grup fa que la pestanya arrossegada s'hi afegeixi. Així que la píndola també va passar a ser una destinació — una pestanya que hi deixis anar cau sense grup, just abans del grup.

Què no arregla la v0.2.0

Tot continua executant-se en un sol procés. L'apunt de backlog sobre l'aïllament de llocs assenyala el seu propi disparador — que arribin les pestanyes al shell — i aquest disparador s'ha activat amb aquesta versió. No s'ha fet. Una pàgina que faci petar Servo s'endú totes les pestanyes, i tots els orígens comparteixen un mateix espai d'adreces.

El panell de DevTools és una pestanya d'un inspector de debò: només Elements, sense consola, sense xarxa, sense depurador. Pinta com a màxim 150 files d'arbre per fotograma i resumeix la resta, perquè tot el shell es torna a renderitzar a cada fotograma mentre Servo fa tick i un arbre sense límit bloquejaria la interfície a 60fps. L'expansió automàtica en amplada que s'hi va escriure està marcada com a codi mort fins que l'arbre no estigui virtualitzat.

La interfície del navegador està en castellà, escrita directament al codi. No configurada en castellà — escrita al codi, com a literals de cadena dins de les funcions de render. No hi ha cap capa de localització ni cap taula de cadenes contra la qual traduir, i les costures ja es veuen: una pestanya sense títol diu «New Tab» quan l'etiqueta ve del motor i «nueva pestaña» quan ve del renderitzador de la barra, a la mateixa finestra.

L'amplada màxima de la barra vertical es va posar a 460 px i es va estrènyer a 320 el mateix matí, que és la mena de xifra que es tria dues vegades perquè ningú no ha decidit per a què serveix. Res d'això no és una llista de coses per a les quals no vam tenir prou temps. És el que va sortir.

active WebView framebuffer background WebView framebuffer discarded dropped — title and URL kept GPUI dma-buf one frame drawn, whatever the tab count a sleeping tab gives its GPU memory back; reopening it reloads the page
Cada pestanya és un WebView viu amb el seu propi framebuffer; només l'activa arriba al compositor, i una pestanya descartada en retorna els dos.