{"slug": "three-years-of-writing-a-browser-engine-in-50-posts", "title": "Three years of writing a browser engine, in 50 posts", "summary": "Gosub Browser project reached its 50th development blog post, marking three years of progress from a Rust novice to a modular browser engine with a multi-process proof-of-concept. The series, written by developer Jaytaph, documents milestones including a tokenizer passing all html5lib-tests, 99.89% of tree-construction tests passing, and a working DNS component.", "body_md": "# Fifty parts of Gosub Browser Progress\n\n2026-08-01 · 1 week ago\n\n# Fifty parts of Gosub Browser Progress\n\nPart 50 went up this week, which felt like a good moment to look back at the whole series. Three years, from \"I don't know Rust and I don't know how browsers work\" to a modular engine with a multi-process proof-of-concept.\n\nHere is the whole thing, one line per part. It reads a bit like a changelog of the project, and a bit like a changelog of me learning Rust.\n\nIt started here: [The one where he is writing a browser...](https://codemusings.nl/@jaytaph/p/MQpHToAx8c1KXyU98Auip4) - the announcement, including the tiny issue that I didn't know Rust yet.\n\n**Tokenizer (pt1-pt9)**\n\n[pt1](https://codemusings.nl/@jaytaph/p/4PGS6LmaYWcqAtLbF2UmW9)- First days with Rust, and how much less explicit it is than Go.[pt2](https://codemusings.nl/@jaytaph/p/FsX6jUZeDKjQHw6Z21tN3E)- Fighting Rust over how to structure the thing. Unclear who won.[pt3](https://codemusings.nl/@jaytaph/p/2QduMFxNzs5kZyjEAMWhPE)- Named character references, finally done. They took far too long.[pt4](https://codemusings.nl/@jaytaph/p/Vf343YLPXyHMtBdc3vxzGA)- Character entities complete, so whole strings tokenize correctly.[pt5](https://codemusings.nl/@jaytaph/p/B73pBMWqjLx2En1W4wGKgi)- More data states filled in, plus generated testcases to check them.[pt6](https://codemusings.nl/@jaytaph/p/Tsbmt1yFWdzS3QDNt9sCU7)- A test harness that runs the tokenizer against the html5 testsuite.[pt7](https://codemusings.nl/@jaytaph/p/F6yS6qRu77VFGg4hs2qu26)- Over 5000 of 6000 token tests passing, with utf-8 as the sticking point.[pt8](https://codemusings.nl/@jaytaph/p/AxuSNVFt8KMH6DpQkp7vKK)- The tokenizer passes all html5lib-tests and lands in main.[pt9](https://codemusings.nl/@jaytaph/p/LDMbsG2rMxHwctHUn2VwfJ)- Tokenizer done, so on to tree construction.\n\n**Tree construction and the borrow checker (pt10-pt23)**\n\n[pt10](https://codemusings.nl/@jaytaph/p/J58UD8m7BzZgdprVXEu5Gk)- Starting to get the hang of Rust. The borrow checker yells less.[pt11](https://codemusings.nl/@jaytaph/p/YSwSALm1ksqT8ix7RipwYN)- Document tree generation runs straight into ownership problems.[pt12](https://codemusings.nl/@jaytaph/p/GGJamNEKiVzW8RxK5tSEHn)- An arena holding all nodes, referenced by node_id.[pt13](https://codemusings.nl/@jaytaph/p/9rcsypRq2U59wvpekjE9Nf)- Still wrestling the borrow checker, with an LLM as a second pair of eyes.[pt14](https://codemusings.nl/@jaytaph/p/M5G27FEJEBDxoecAN9QzL8)- First attempts at the adoption agency algorithm.[pt15](https://codemusings.nl/@jaytaph/p/WxUbXppbk6r2gzMGX9QSFd)- Something snapped and the borrow checker finally made sense.[pt16](https://codemusings.nl/@jaytaph/p/WFBnHUPqo4CYmbZeg8uLP8)- A blog post on why this project exists, which blew up on Hacker News.[pt17](https://codemusings.nl/@jaytaph/p/C3QbhU3XeTA4CBVSLwdG2u)- Still stuck on test 23 of the tree-construction tests.[pt18](https://codemusings.nl/@jaytaph/p/CinQuVMDzW9i1DoTSnUFYX)- Contributions start coming in, and my long-lived branch gets out of sync.[pt19](https://codemusings.nl/@jaytaph/p/TUosNXnVo4X11PrTRKBYED)- Test 23 defeated. The adoption agency works.[pt20](https://codemusings.nl/@jaytaph/p/2EGZHQNSXjS8MHXpgRZqC5)- Filling in more tests, and struggling with Rust while doing it.[pt21](https://codemusings.nl/@jaytaph/p/3v3FF4xoKR2xvMx9prpW5T)- All 112 tests in test1.dat pass. Many files to go.[pt22](https://codemusings.nl/@jaytaph/p/M7PiHzkWVx4m9qJKT25LMe)- Parsing live documents from the actual web.[pt23](https://codemusings.nl/@jaytaph/p/QbGZoeUveUSHNLoZeFbC2d)- 99.89% of the tree-construction tests pass.\n\n**Network stack and CSS (pt24-pt30)**\n\n[pt24](https://codemusings.nl/@jaytaph/p/XMZpPyxQ4tSsSwWjDxvgxy)- Designing the whole flow, and realising how much is still unwritten.[pt25](https://codemusings.nl/@jaytaph/p/9KT5NRKrq5xyYA7gG8Le4r)- The DNS component is finished.[pt26](https://codemusings.nl/@jaytaph/p/243523MYmbn414zmN8tRBw)- A week of yak shaving on the way to a http-to-rendertree pipeline.[pt27](https://codemusings.nl/@jaytaph/p/YLB7aCTNMMj89ogDR2ezdq)- A working css3 parser, at the cost of the previous one.[pt28](https://codemusings.nl/@jaytaph/p/8npkEJtf2cx8bmFuGdcJwi)- Porting csstree from JavaScript to Rust, which goes better than expected.[pt29](https://codemusings.nl/@jaytaph/p/CnBMTLHSMZmMrTMhodXuEd)- Real-life CSS parses, at the cost of ignoring the rest of the engine.[pt30](https://codemusings.nl/@jaytaph/p/D1frA2X95PukV1A23z9e8T)- Tying the html parser and the css3 parser together.\n\n**First pixels (pt31-pt43)**\n\n[pt31](https://codemusings.nl/@jaytaph/p/7awizheLS23YtFWS77mxKR)- \"dive\", a text-based user agent built with ratatui.[pt32](https://codemusings.nl/@jaytaph/p/KuxRowHetzgaDSmpiXrU9V)- Tree-walking to get rudimentary text on screen.[pt33](https://codemusings.nl/@jaytaph/p/RcsFyWGc5ZzV1zsv3peVk4)- Turning a CST into stylesheet structures usable for selectors.[pt34](https://codemusings.nl/@jaytaph/p/LxaWzBWGhpQdpNdtEzSHjZ)- Slower going, with the CSS system still far from finished.[pt35](https://codemusings.nl/@jaytaph/p/FuYrqZju7JCiQagSNWQsga)- Refactoring to break the cyclic dependency between html5 and css3.[pt36](https://codemusings.nl/@jaytaph/p/MAxTbp6b2BWF1GzxQkL7bt)- The CSS3 and HTML5 crates finally work together.[pt37](https://codemusings.nl/@jaytaph/p/6oJ1dnx5LcshRSx5EfhAGm)- Taking stock of the components, and thinking about event loops and async.[pt38](https://codemusings.nl/@jaytaph/p/SUMjLniA2roBqUL8e4Yasq)- The v2 engine proof-of-concept merges into main.[pt39](https://codemusings.nl/@jaytaph/p/1xGnBMMqNGUw7T2RaksdiB)- We've got a browser: a standalone useragent that loads tabs and renders them.[pt40](https://codemusings.nl/@jaytaph/p/C8TqsM9mgKzfsKENN9XCas)- Typography work, after the planning went sideways as usual.[pt41](https://codemusings.nl/@jaytaph/p/FvfgpqDKC757EVP1SPMTdg)- Font manager setbacks: the font libraries are not interchangeable.[pt42](https://codemusings.nl/@jaytaph/p/A4e58uNvkzDHtsCmzjHEKd)- The render pipeline split into separate phases, tiling and painting included.[pt43](https://codemusings.nl/@jaytaph/p/9ibagj6xxE6sERk4vfeq6W)- The rendering pipeline PoC turns html and css into actual pixels.\n\n**Putting it together (pt44-pt50)**\n\n[pt44](https://codemusings.nl/@jaytaph/p/XcjEE2cD4ic8sMKjxFyThu)- Back at it after a quiet stretch, now with AI in the toolbox.[pt45](https://codemusings.nl/@jaytaph/p/L5pxfiwMzMmNtNMAXSKTma)- The big three merged: network stack, rendering pipeline and engine api.[pt46](https://codemusings.nl/@jaytaph/p/3mzKJYonyAtac3vg2BfRvP)- Modularity pays off, with several browsers built on the same engine.[pt47](https://codemusings.nl/@jaytaph/p/UHWnwWdBtEtfonmjNKfW9Q)- A new gosub.io, with a nautical theme.[pt48](https://codemusings.nl/@jaytaph/p/C6KrES8iUP4VUUa81DEmNE)- The engine back in a useful state, with ModuleConfig and RenderConfig.[pt49](https://codemusings.nl/@jaytaph/p/TR39RSNTmGZP9FrKiNa2j4)- A refurbished Mac M1 to build and test on real hardware.[pt50](https://codemusings.nl/@jaytaph/p/SyyydtLwLL4Z9wZFQ525jb)- Cleanup, and a multi-process proof-of-concept.\n\nOn to the next fifty.\n\nNobody amused yet.", "url": "https://wpnews.pro/news/three-years-of-writing-a-browser-engine-in-50-posts", "canonical_source": "https://codemusings.nl/@jaytaph/p/9A6TCthSHwFEZwGbzSumbN", "published_at": "2026-08-12 07:58:28+00:00", "updated_at": "2026-08-12 08:11:14.515455+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Gosub Browser", "Jaytaph", "Rust"], "alternates": {"html": "https://wpnews.pro/news/three-years-of-writing-a-browser-engine-in-50-posts", "markdown": "https://wpnews.pro/news/three-years-of-writing-a-browser-engine-in-50-posts.md", "text": "https://wpnews.pro/news/three-years-of-writing-a-browser-engine-in-50-posts.txt", "jsonld": "https://wpnews.pro/news/three-years-of-writing-a-browser-engine-in-50-posts.jsonld"}}