This is finally approaching a usable state: navigates and renders Wikipedia, supports TLS, builds on Windows, macOS and Linux, loads PNGs and JPEGs, etc.
I'm going to opensource this once it supports most of CSS 2.2 layout and renders HN too (HN is totally blank right now, because it's a giant <table> which is still a TODO :-) )
I still don't know what to do with this. It's been a huge passion project and I'm very happy about it. I planned to create a book or video course about C programming, starting from printf("Hello world!\n") and ending up with a working browser; but the advent of AI coding agents in the past 2 years changed a lot the market for such a course.
I'm still finishing this due to stubbornness and some sunken cost fallacy effect :-)
Would love to hear feedback and ideas about such a project.
What I'm saying is, I think this kind of thing will help the young'uns realize they can do it too. That fact is, unfortunately, far from obvious. One of the worst anecdotes I have from elementary school was of asking the librarian (who also oversaw the computer lab) who made OpenOffice, because I wanted to learn about how software works and figured that'd be a good start, and hearing her immediately dismiss the question by saying "People a lot smarter than us!" in front of the whole class. I can only hope nobody internalized that.
Also, out of curiosity from this topic being heavily on my brain lately, might I ask how you load images? Are you using libraries, or did you write any decoders yourself? Back in the day, my far less experienced self tried writing a BMP decoder, then learned about fuzzing and broke it instantly. Now that was a fun learning experience.
I recently did some playing around with having AIs test PNG decoders and it was as much of a mess as I expected. Broke a few in under an hour from casual prompting, and found weird bugs and oversights in every single one over a few days of not-so-casual prompting. (If you want to check reliability yourself, I found it eye-opening to run against a test corpus made from combining every major project's own corpus, and checking the pixel output, not just whether or not it crashed or ate all your RAM.)
I love how John Carmack put it: [1]
You can't know everything, but you should convince yourself that you can know anything.
The Handmade Hero series [2] channels this feeling really well, and I wanted to convey it again by building a browser from scratch but with a limited scope (Wikipedia, HN; no Javascript, but also no libraries and no AI).I also loved tutorials in the 90s like "build your own OS", "build your own bootloader", "build your own C compiler", etc; build your own printf(), your own malloc, etc. Learned a lot this way!
might I ask how you load images? Are you using libraries, or did you write any decoders yourself?
No libraries means no libraries! :-) The decoders are written by hand, following the specs. They don't support every single configuration (e.g. JPEG has TONS of variants), but good enough for the media in Wikipedia.This browser is a learning experience and it's definitely NOT secure (e.g. TLS "works" but it doesn't verify server certificates at all...). The decoders use a set of shared utilities for decoding big-endian/little-endian, 16 bits, 32 bits, etc and checking for overflows (using setjmp/longjmp for convenience). I think it's pretty good, and I also bet that Claude would find many problems. I'm prioritizing (1) code that works, then (2) code that's performant, and then (3) code that's clear.
I'm ambivalent about throwing AI at this codebase. It would definitely find issues and improve it, but I also want to keep the spirit of "Do It Yourself" and the feeling of "We built this and it works".
[1] Searched the quote from memory, Google found it here: https://transcript.baribarilab.com/video/HOv_dWDgIRM/
Actually, what sold me on bothering with LLMs was asking one, last year, how to call a kernel syscall with GCC's inline assembly syntax, and seeing the answer be exactly what I needed... after I had already cobbled everything together myself! (I do not consider random mailing list archives to be adequate documentation!)
I didn't mean to say you should have an AI inspect your code, though. (I've personally found value in that technique, because current frontier models can recognize problems way better than their lackluster ability to avoid writing those same problems repeatedly into their own disgusting code. I can still just write the code myself, with another eye on it.)
I just really like fuzzing and really like differential analysis. For example, one of the images in my test suite decodes to one set of pixels with FFmpeg, libpng, stb_image, among others, and decodes to a different set of pixels with libspng, lodepng, Java's ImageIO, and other others (as far as I can tell, the reason is ambiguous 2nd-ed spec wording that isn't obviously ambiguous when you read it). You can quite easily fuzz one implementation against another, running them in parallel against the same inputs, and make a mismatch crash the test runner so the fuzzer flags an error.
Incidentally, asking Claude Fable 5 to do that usually triggers a cybersecurity block for me. Another point in favor of knowing things by hand...
Not that I'm saying you should drop everything and do this. I just like talking about it. Learning-experience projects get easily spoiled by trying too hard, which I did learn the hard way. Then again, I hella enjoyed practicing becoming aware of code coverage when I wrote a few prototype compilers. Researching online when I was making my first one is how I learned about Jonathan Blow and Casey Muratori, by the way. I don't follow Handmade Hero, but I do wholeheartedly approve of its existence.
I also loved tutorials in the 90s like "build your own OS", "build your own bootloader", "build your own C compiler", etc; build your own printf(), your own malloc, etc. Learned a lot this way!
You must be older than me! I was born in the 90s, and the usual reaction I expect to saying I built my own memory allocator is incredulity because "why make something that already exists?" or "ew, you should've used Rust".I haven't run across that Carmack quote before, but I've told plenty of people the same thing in my own words. I think one of the most important parts of it that I've learned is this: You can't get something right without screwing it up first. I don't mean that making mistakes is a goal to target. I mean that you can't learn without the errors along the way. If you're always right the first time, that's just luck, and you haven't learned yet.
And I must say, I miss the days when "grok" was a weird word some programmers use, not an AI brand.
Anyway, I'd be quite curious to see your project if you ever put it up in public. I wouldn't worry about polishing it, really. In high school, I still didn't know people normally write rough drafts of stories and draw sketches long before making the finished piece. Never saw any of that, so I thought my inability to "oneshot" these things with the results of a high-skill artist meant inherent lack of talent. If all you ever see is finished refined products, how can you possibly know better?
So you’re implementing TLS (including RSA, elliptic curve, etc), JPEG decoding and PNG decoding from scratch? :)
TLS is always version 1.2, and only the mandatory cypher suites are supported: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 and TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256.
This requires SHA256, AES and two elliptic curves (x25519 and secp256r1).
AES is used in GCM mode, so that's implemented too.
However, the browser doesn't verify the server signature over its certificate. So ECDSA and RSA are not implemented :-)
Getting TLS to work was indeed one of the more frustrating parts. The cryptographic functions are very well documented and there are lots of good test vectors, so that wasn't too hard (just a lot of work). But getting TLS working was very frustrating -- the handshake would complete, I'd switch to encrypting mode, and... the server would just disconnect my client instantly, with no further feedback or a stacktrace or anything.
I ended up debugging that by building openssl with LOTS of printfs all over the place and fixed nonces, talking to a local HTTPS server, and tried to make my client emit exactly the same bytes until I found the issue -- memory corruption, of course :-)
I built on amazing tutorials available online. For example, elliptic curve cryptography and x25519 in particular are SUPER well explained at [1].
JPEG was fun too. Tutorials like [2] gave a great overview of how it works, and the spec [3] is super clear (but includes TONS of optional stuff that can be skipped)
PNG was the easiest, the RFC is very straightforward [4].
(I actually did the PNG decoder before doing fonts, because the TTF format is so crazy that I always decided to using bitmaps fonts loaded via PNGs instead)
It's super satisfying to build ~300 sources in ~2 seconds from scratch using just MSVC, Xcode or Make, and then launching Wikipedia -- no libraries, and it works :-)
[1] https://www.cl.cam.ac.uk/teaching/2122/Crypto/curve25519.pdf
[2] https://www.youtube.com/watch?v=0me3guauqOU
No Javascript :-) React sites are completely blank, like instagram.com.
No GL/DirectX/Vulkan. It's all software rendered and it just blits a pixel array to screen on every frame. Highly portable though!
Font rendering is antialiased but it's not hinted, and small glyphs at low DPI look start looking blurry.
No Wayland support, only X11 because it's the most widely available. I might do a Wayland port later.
All in one process, one thread per tab. No process isolation (also something to do later)
Only HTTP/1.1 and TLS 1.2, no HTTP2 or later. No IPv6, only IPv4.
No SVG, no <canvas>, no form controls (buttons, etc.), no cookies support (for now...)
The CSS support however is bigger than CSS 2.2: there's var(), --custom-properties, @media queries, etc. and I'm planning to do flex and grid layout too (because Wikipedia uses that a lot).
Just looked at a few more metrics:
v1 was 148 commits over about a year; gave up when it came to TTF font parsing and rendering.
v2 was 1075 commits over 3 months and in C++, gave up after doing sockets but realizing that TLS and crypto was going to be required. I also strongly disliked the C++ codebase :-)
The current repo is v3 and back in C, started in Oct 2024 (2 years), and it's 4822 commits. About 600 .c and .h files, about 68k lines of code (14k blank, 7k comments).
I had a lot of previous experience with networking and systems programming, but not with rendering.
A huge inspiration was https://guide.handmadehero.org/ and Bob Nystrom's https://www.craftinginterpreters.com. I wanted to create a similar book and video series, but by building a browser.
I'm currently planning to opensource early next year and I will definitely post to HN.
(The original name for this project was "Hacker's Browser", as in, an HN browser :-) But then I figured that Wikipedia worked pretty well and grew in scope from there...)
I'll also be looking for early testers soon, to verify that this thing builds on various platforms before the public release. If you're open to try this early on then see my profile to get in contact.