Iconos, texto y el árbol
Las cosas pequeñas que dicen que el navegador es real
Una pestaña sin icono, un logo que se dibuja como espacio vacío, una ventana que un lector de pantalla no puede describir. Ninguna de estas cosas impide que una página funcione, y todas son la diferencia entre algo que renderiza HTML y algo que una persona usaría. Este lote es de ese tipo de trabajo.
/favicon.ico, que ninguna norma describe
Servo solo cogía el favicon de <link rel=icon>. Muchos sitios no declaran ese enlace — crates.io y old.reddit.com entre bastantes más — y confían en que el icono se sirva desde /favicon.ico en la raíz del origen. El estándar de HTML no describe ese fallback. Todos los navegadores lo hacen igualmente, así que un sitio que depende de él no está haciendo nada raro; está dependiendo del comportamiento y no de la especificación.
Un documento de nivel superior que llega al estado interactive sin haber visto ningún enlace de icono pide ahora /favicon.ico. Esperar a interactive es lo que evita la carrera: un elemento link parseado tarde sigue ganando, y el fallback solo corre cuando el parser ya ha tenido su oportunidad de encontrar uno.
Un mensaje sin dónde aterrizar
Un favicon vectorial se rasteriza fuera del hilo principal, así que la primera llamada a rasterize_vector_image devuelve None y quien llama se registra para el mensaje de fin. Window::handle_image_rasterization_complete_notification solo miraba pending_images_for_rasterization, la lista que guarda layout para poder marcar un nodo sucio cuando una imagen termina.
Un favicon no tiene nodo en esa lista. No está en el documento; es una propiedad de la página que el embedder dibuja en su propio chrome. Así que el mensaje de fin llegaba, no coincidía con nada y se descartaba, y todos los sitios con icono SVG no mostraban ninguno. El registro de un favicon se mira ahora también, así que el icono llega cuando acaba su rasterización.
Un icono que sobrevivía a su documento
WebViewInner.favicon solo se asignaba, nunca se reseteaba. Navega de un sitio que tiene icono a uno que no, y el icono viejo se quedaba en la barra de pestañas el resto de la vida de esa pestaña — el resto de la vida, no hasta el siguiente icono, porque un sitio sin icono no manda nunca uno que lo reemplace.
El icono pertenece al documento, así que ahora se descarta cuando empieza una carga de nivel superior, y se le dice al embedder, igual que se le dice cuando llega uno nuevo.
Familias de fuente genéricas, apuntando a algo real
fontdb arranca nombrando fuentes de Windows para las familias genéricas: "Times New Roman" para serif, "Courier New" para monospace. usvg cae en las mismas para un SVG que no nombra ninguna familia. En un sistema sin esas fuentes, cada genérica resuelve a nada, usvg descarta el texto que pedía una, y el SVG se rasteriza sin él.
Un icono dibujado con <text> sale entonces totalmente transparente. El favicon de syrakon.com es un logotipo en monospace, así que era invisible — e invisible de una manera que parece el código del icono fallando y no la base de datos de fuentes, que es por lo que costó encontrarlo. Las familias genéricas apuntan ahora a fuentes instaladas.
Uno más de la misma familia de bug: Servo reflejaba un <a> dentro de un <svg> al SVGElement pelado, y el global SVGAElement no existía en absoluto. Un script que lo nombra lanza ReferenceError: SVGAElement is not defined — y el runtime de SvelteKit lo nombra, cuando decide cómo tratar el objetivo de un clic. La promesa rechazada se llevaba por delante el resto de ese módulo, así que todo lo que el módulo iba a renderizar no aparecía nunca. La interfaz es un stub que hereda de SVGGraphicsElement, que es suficiente para que el nombre resuelva.
Una ventana que se puede leer en voz alta
El árbol de elementos de GPUI es presentacional. Nada en él dice que un div es un botón o que una tirada de glifos es la etiqueta de ese botón, que es exactamente la información que necesita un lector de pantalla y exactamente la que un renderer optimizado para dibujar no guarda.
Los elementos lo declaran ahora, opt-in y de forma explícita:
div().a11y(AccessRole::Button, "Recargar")
Cada elemento anotado emite un AccessNode durante el paint, con rol, etiqueta, valor, estado de selección, los bounds pintados y el focus handle que sigue. Los nodos se acumulan en el frame en orden de paint, lo que le da al árbol un orden de lectura gratis — el orden de paint se parece bastante al orden de lectura en un chrome montado como el de rumb.
Las ventanas de Wayland y X11 convierten la lista de nodos de cada frame presentado en un árbol de AccessKit y lo empujan a accesskit_unix, que habla AT-SPI con Orca y con el resto de la tecnología asistiva del escritorio. AccessKit es agnóstico del servidor gráfico — es D-Bus, no Wayland ni X11 — así que los dos backends comparten un puente en vez de criar uno cada uno.
Una rueda que se desliza
Un paso de rueda movía la página de un salto, que es correcto y se siente como una barra de scroll de 1998. WebView::glide_wheel_event reparte el scroll de un evento de rueda entre los frames siguientes, una vez que el script ha atendido el evento sin cancelarlo: cada frame cubre una parte de lo que queda, en píxeles enteros, así que la página arranca rápido y frena suave con el frame rate que haya. Girar la rueda al otro lado descarta lo que quedaba en vez de pelearse con ello.
Píxeles enteros por frame es la parte que hay que conservar. Una fracción de la distancia restante es una serie geométrica, y en píxeles fraccionarios no llega nunca del todo; redondear al alza el paso de cada frame a un píxel es lo que hace que termine.
Servo no pinta barras de scroll, así que el shell tiene que dibujarlas, y hasta ahora no tenía manera de saber cuánto podía desplazarse la página. WebView::viewport_scroll_extent lo informa.
Lo que un lector de pantalla todavía no puede hacer
El árbol tiene un nivel de profundidad: un nodo raíz para la ventana, etiquetado con su título, y los elementos anotados debajo. No hay anidamiento, así que nada expresa que un botón está dentro de una barra de herramientas dentro de una tira de pestañas.
La página misma no está en ese árbol en absoluto. Servo tiene su propia historia de accesibilidad y este puente describe solo el chrome, lo que significa que un lector de pantalla puede encontrar el botón de recarga y ni un solo encabezado de la página que recarga. La navegación por teclado del chrome es la otra mitad de este trabajo y no está hecha.
Y las etiquetas están en español, hardcodeadas, como el resto de las cadenas del chrome — "Recargar" no es un placeholder en ese ejemplo, es lo que dice el código.