Un bloquejador, i el que deixava quiet YouTube
Onze dies després de quatre entrades
Les quatre últimes entrades, del 2026-10-10, explicaven com el motor cedeix decisions a l'incrustador, com YouTube va arribar a reproduir i què guarda el navegador. Van deixar dues coses obertes. El shell tenia els ganxos per a un bloquejador de contingut i el web no n'ensenyava cap, i YouTube reproduïa amb aturades mesurades i sense causa.
Totes dues s'han mogut. rumb ja porta el seu propi bloquejador, cuirassat, i les aturades van resultar ser quatre errors diferents, un d'ells obra del mateix bloquejador.
Cuirassat
Cuirassat està fet sobre el motor adblock de Brave i treballa a tres profunditats, cadascuna sobre un ganxo que van descriure les entrades anteriors.
Per xarxa, amb EasyList i EasyPrivacy: el filtre de peticions rebutja abans que surti una petició que les llistes anomenen. Per full d'estils: el que les llistes demanen amagar s'amaga, amb un full d'usuari que es canvia per pàgina. I per script, per al que no arriba cap de les dues.
Un escut a la barra ensenya quant s'ha bloquejat a la pàgina que tens davant, amb un interruptor per deixar de bloquejar en aquell lloc. Hi ha tres nivells. Lleu es queda en els anuncis. Moderat hi afegeix rastrejadors i les llistes d'uBlock. Dràstic hi suma avisos de galetes, altres molèsties i servidors de risc, sabent que alguna pàgina es trencarà.
Hi ha anuncis que no es poden tallar per xarxa, perquè arriben des de la mateixa adreça que el contingut, dins de la resposta que la pàgina demana per a ella mateixa. Per a aquests cal codi que corri a la pàgina abans que el seu. Cuirassat porta els seus propis scriptlets, amb els noms i arguments que fan servir les llistes d'uBlock: la llista decideix quin s'executa, però el codi que corre és el de rumb. Els que reescriuen respostes només s'accepten de llistes marcades com a de confiança.
A YouTube, amb les tres, els anuncis desapareixen.
L'aturada que va causar el bloquejador
Els scriptlets arriben a la pàgina com a scripts d'usuari, i alguns funcionen embolcallant una funció de la plataforma. Allà hi havia la primera aturada.
load_script executava els scripts d'usuari cada vegada que un element head s'enganxava a un arbre. Un document sense context de navegació —el que creen DOMParser o createHTMLDocument— té igualment la finestra de la pàgina que el va crear, així que el seu head tornava a executar els scripts de la pàgina. I el mateix un segon head a la mateixa pàgina.
Un script que embolcalla una funció l'embolcallava una vegada més a cada passada. En una pàgina de vídeo de YouTube els scripts s'executaven 57 vegades, i JSON.stringify passava per 342 embolcalls que copien cadascun el seu argument. Una crida amb 325 kB trigava 1200 ms en lloc de 24 ms. La pàgina fa una crida així cada pocs segons, i això aturava el fil de script, i amb ell el vídeo, durant 0,8 s.
Ara els scripts s'executen una vegada per finestra, i només per al head d'un document amb context de navegació.
Tres aturades més, totes del motor
Cada fotograma d'un vídeo marcava el seu element com a danyat, així que el fil de script refeia la maquetació i la llista de pintat de tota la pàgina per a cadascun. Un fotograma de la mida de l'anterior entra ara a la imatge que la llista de pintat ja mostra, i es demana al pintor que torni a dibuixar des del fil que el va portar. Reproduint 1080p a 30 fotogrames per segon, el buit més llarg entre dos pintats ha passat de 147–183 ms cada deu segons a 50 ms.
timeupdate no es disparava mai durant la reproducció d'un element multimèdia els textTracks del qual ningú havia tocat: time_marches_on tornava abans del pas que l'encua. El reproductor de YouTube es pensava que encara era al segon 0 i deixava de demanar dades quan tenia 20 s a la memòria intermèdia.
I un temporitzador cancel·lat per un altre que vencia a la mateixa tanda s'executava igualment, perquè clearTimeout només mirava a la llista d'espera i fire_timer ja n'havia tret tots els vençuts. Per això la roda del reproductor es quedava a la pantalla durant la reproducció.
Una altra era lentitud més que aturada. Cada part d'un Blob és una arrel pròpia, i el conjunt d'arrels busca la que ha de deixar anar des de la seva entrada més nova cap enrere. YouTube munta un Blob de 54 000 parts de text a cada navegació, i deixar-les anar de la més vella a la més nova trigava 729 ms. Ara es deixen anar de la més nova a la més vella.
Reproductors que no es deixaven anar mai
Els gestors de l'adaptador de senyals i del pipeline subjectaven el reproductor intern amb una referència forta, i el reproductor intern és amo de tots dos, així que no s'alliberava cap reproductor. Cada canvi de font d'un element multimèdia deixava enrere un fil de GStreamer, amb un pipeline, el seu descodificador i, amb source buffers, tots els fotogrames codificats. Una pàgina que carrega un fitxer vuit vegades i enganxa un MediaSource vuit vegades acabava amb 16 fils de reproductor, 8 sessions de NVDEC i 1645 MB residents. Ara acaba amb 1, 1 i 632 MB.
Alliberar-los va destapar l'error següent. Un missatge del reproductor guarda una referència al reproductor, així que quan el fil que l'enviava deixava anar l'última, el reproductor es destruïa al fil que espera. Es veia com un segfault a libgobject quan YouTube canviava de vídeo, dues vegades en 114 canvis. Ara un fil recol·lector deixa anar els reproductors, cosa que a més treu aquesta espera del fil de script.
I un vídeo silenciat silenciava el següent. El servidor d'àudio recorda el volum i el silenci per nom d'aplicació i els dona a cada flux nou, així que una pàgina que silenciava un vídeo deixava mut el següent. El volum i el silenci s'apliquen ara dins del pipeline, i un flux s'obre amb els valors del seu reproductor.
MSE ha guanyat el que demanen l'àudio de YouTube i el seu menú de qualitat: SourceBuffer.mode = "sequence", source buffers audio/mpeg i audio/aac, getVideoPlaybackQuality() comptat on els fotogrames arriben al renderitzador, i navigator.mediaCapabilities.decodingInfo().
Fora del multimèdia, una crida a la consola en una pàgina de directe de Twitch enviava a les devtools una descripció de tot el que abastava l'objecte registrat, hi hagués client o no, i la pàgina creixia 700 MB per segon fins que el sistema matava el navegador. La descripció s'atura ara a tres objectes de profunditat. I un camp de text buit i amb el focus torna a ensenyar el cursor.
El que queda
Continua sense haver-hi EME, així que res darrere de DRM es reprodueix, i els còdecs són els que donin els connectors de GStreamer de la màquina. El següent al backlog són les baixades amb quarantena i un mode de bloqueig total.
rumb encara no substitueix el teu navegador. Cada setmana li falta menys.