← bitàcola

Icones, text i l'arbre

Les coses petites que diuen que el navegador és real

Una pestanya sense icona, un logotip que es dibuixa com a espai buit, una finestra que un lector de pantalla no pot descriure. Cap d'aquestes coses no impedeix que una pàgina funcioni, i totes són la diferència entre una cosa que renderitza HTML i una cosa que una persona faria servir. Aquest lot és d'aquesta mena de feina.

/favicon.ico, que cap norma no descriu

Servo només agafava el favicon de <link rel=icon>. Molts llocs no declaren aquest enllaç — crates.io i old.reddit.com entre bastants més — i confien que la icona se serveixi des de /favicon.ico a l'arrel de l'origen. L'estàndard d'HTML no descriu aquest fallback. Tots els navegadors ho fan igualment, així que un lloc que en depèn no fa res estrany; depèn del comportament i no de l'especificació.

Un document de nivell superior que arriba a l'estat interactive sense haver vist cap enllaç d'icona demana ara /favicon.ico. Esperar a interactive és el que evita la cursa: un element link analitzat tard continua guanyant, i el fallback només corre quan l'analitzador ja ha tingut la seva oportunitat de trobar-ne un.

Un missatge sense on aterrar

Un favicon vectorial es rasteritza fora del fil principal, així que la primera crida a rasterize_vector_image retorna None i qui crida es registra per al missatge de fi. Window::handle_image_rasterization_complete_notification només mirava pending_images_for_rasterization, la llista que guarda layout per poder marcar un node brut quan una imatge acaba.

Un favicon no té node en aquesta llista. No és al document; és una propietat de la pàgina que l'embedder dibuixa a la seva pròpia interfície. Així que el missatge de fi arribava, no coincidia amb res i es descartava, i tots els llocs amb icona SVG no en mostraven cap. El registre d'un favicon es mira ara també, així que la icona arriba quan acaba la seva rasterització.

Una icona que sobrevivia al seu document

WebViewInner.favicon només s'assignava, mai no es reiniciava. Navega d'un lloc que té icona a un que no, i la icona vella es quedava a la barra de pestanyes la resta de la vida d'aquella pestanya — la resta de la vida, no fins a la icona següent, perquè un lloc sense icona no n'envia mai cap que la reemplaci.

La icona pertany al document, així que ara es descarta quan comença una càrrega de nivell superior, i se li diu a l'embedder, igual que se li diu quan n'arriba una de nova.

Famílies de lletra genèriques, apuntant a alguna cosa real

fontdb arrenca anomenant lletres de Windows per a les famílies genèriques: "Times New Roman" per a serif, "Courier New" per a monospace. usvg cau en les mateixes per a un SVG que no anomena cap família. En un sistema sense aquestes lletres, cada genèrica resol a res, usvg descarta el text que en demanava una, i l'SVG es rasteritza sense ell.

Una icona dibuixada amb <text> surt aleshores totalment transparent. El favicon de syrakon.com és un logotip en monospace, així que era invisible — i invisible d'una manera que sembla el codi de la icona fallant i no la base de dades de lletres, que és per això que va costar trobar-lo. Les famílies genèriques apunten ara a lletres instal·lades.

Un més de la mateixa família de bug: Servo reflectia una <a> dins d'un <svg> a l'SVGElement pelat, i el global SVGAElement no existia gens. Un script que l'anomena llança ReferenceError: SVGAElement is not defined — i el runtime de SvelteKit l'anomena, quan decideix com tractar l'objectiu d'un clic. La promesa rebutjada s'enduia la resta d'aquell mòdul, així que tot el que el mòdul havia de renderitzar no apareixia mai. La interfície és un stub que hereta de SVGGraphicsElement, que és prou perquè el nom resolgui.

ONE PRESENTED FRAME an element .a11y(Button, "Recargar") AccessNode role · label · value bounds · focus handle AccessKit tree AT-SPI over D-Bus nodes accumulate in the frame in paint order; nothing else in GPUI's element tree says a div is a button Wayland window · X11 window one shared bridge, because D-Bus is neither The tree is one level deep: a root node for the window, labelled with its title, and the annotated elements of the frame beneath it.

Una finestra que es pot llegir en veu alta

L'arbre d'elements de GPUI és presentacional. No hi ha res que digui que un div és un botó o que una tirada de glifs és l'etiqueta d'aquell botó, que és exactament la informació que necessita un lector de pantalla i exactament la que un renderer optimitzat per dibuixar no guarda.

Els elements ho declaren ara, opt-in i de manera explícita:

div().a11y(AccessRole::Button, "Recargar")

Cada element anotat emet un AccessNode durant el paint, amb rol, etiqueta, valor, estat de selecció, els bounds pintats i el focus handle que segueix. Els nodes s'acumulen en el frame en ordre de paint, cosa que dóna a l'arbre un ordre de lectura de franc — l'ordre de paint s'assembla prou a l'ordre de lectura en una interfície muntada com la de rumb.

Les finestres de Wayland i X11 converteixen la llista de nodes de cada frame presentat en un arbre d'AccessKit i l'empenyen a accesskit_unix, que parla AT-SPI amb l'Orca i amb la resta de la tecnologia assistiva de l'escriptori. AccessKit és agnòstic del servidor gràfic — és D-Bus, no Wayland ni X11 — així que els dos backends comparteixen un pont en lloc de criar-ne un cadascun.

Una roda que llisca

Un pas de roda movia la pàgina d'un salt, que és correcte i se sent com una barra de desplaçament del 1998. WebView::glide_wheel_event reparteix el desplaçament d'un esdeveniment de roda entre els frames següents, un cop l'script ha atès l'esdeveniment sense cancel·lar-lo: cada frame cobreix una part del que queda, en píxels sencers, així que la pàgina arrenca ràpid i frena suau amb el frame rate que hi hagi. Girar la roda a l'altre costat descarta el que quedava en lloc de barallar-s'hi.

Píxels sencers per frame és la part que cal conservar. Una fracció de la distància restant és una sèrie geomètrica, i en píxels fraccionaris no arriba mai del tot; arrodonir cap amunt el pas de cada frame a un píxel és el que fa que acabi.

Servo no pinta barres de desplaçament, així que el shell les ha de dibuixar, i fins ara no tenia manera de saber quant es podia desplaçar la pàgina. WebView::viewport_scroll_extent ho informa.

El que un lector de pantalla encara no pot fer

L'arbre té un nivell de fondària: un node arrel per a la finestra, etiquetat amb el seu títol, i els elements anotats a sota. No hi ha imbricació, així que res no expressa que un botó és dins d'una barra d'eines dins d'una tira de pestanyes.

La pàgina mateixa no és en aquest arbre gens. Servo té la seva pròpia història d'accessibilitat i aquest pont descriu només la interfície, cosa que vol dir que un lector de pantalla pot trobar el botó de recàrrega i ni un sol encapçalament de la pàgina que recarrega. La navegació per teclat de la interfície és l'altra meitat d'aquesta feina i no està feta.

I les etiquetes són en castellà, posades a pèl al codi, com la resta de les cadenes de la interfície — "Recargar" no és un placeholder en aquell exemple, és el que diu el codi.

ONE DOCUMENT, ONE ICON <link rel=icon> all Servo ever read /favicon.ico no link seen, and interactive reached rasterize, off-thread first call returns None completion delivered to the tab bar a favicon has no node in pending_images_for_rasterization, so the message used to be dropped and the icon never arrived The icon belongs to the document: a top-level load starting drops it and tells the embedder, so a tab no longer keeps the icon of the first page that had one. and in an SVG icon, a generic font family now resolves to an installed font, not a Windows one
Tres errors diferents entre una pàgina i la seva icona, cadascun prou per si sol per deixar la pestanya en blanc.