A web browser made from scratch in Rust with a custom HTTP Client, HTML+CSS parser and renderer, and JS interpreter.
A web browser made from scratch in Rust with a custom HTTP Client, HTML+CSS parser and renderer, and JS interpreter.

harbor: and is set as the default New Tab page.R and not a refresh icon because the font I’m using doesn’t have a refresh icon.Cmd + T for new tab, Opt + Tab for switching to the next tab, Opt + Shift + Tab for switching to the previous tab.!important be respected.Log in to leave a comment

<link> tag in HTML documents. I’m pretty sure this has a bug that means relative paths won’t work. I haven’t tested that but I know I didn’t implement it, so it probably doesn’t work.Log in to leave a comment

TabData struct store the tab’s document and layout objects (well, pointers to them) to allow for reusing layouts when switching between tabs. This was a nice performance improvement.<title> element (and fallback to the url if there is no title). You can now actually tell that there are multiple tabs open.I hate that the catp color colors (blue, red, etc.) contrast so poorly with the text color that they’re genuinely unreadable. I had to use a reskinned version of the Surface 2 color to get a nice color for the active tab that still has good contrast with the text.
Log in to leave a comment

http and https scheme so I added the file scheme too since it wasn’t too much work. I can now open local files in a more modern-browser way kinda way.- opens a background tab with URL https://flavorless.hackclub.com/ . You can navigate between tabs using the number keys. Obviously, this is all temporary stuff just to get a MVP.
PrimaryExpression for now, but it’s a start.Debug impls to make it easier to see the output. It’s definitely working!Log in to leave a comment

(I keep procrastinating)
I haven’t devlogged in 3 days mainly because exams but I’ve been working on this a lot too (11 commits since the last devlog). The progress just wasn’t significant enough to warrant a devlog.
PS: Screenshot is taken from code executed by Rust
Log in to leave a comment

(I’m dumb)
extern struct in Rust. It’s kind of annoying to call some of the functions. I might have to write some wrapper functions in Rust to make it easier to call them.
line-height to calculate their position rather than content height. In the previous devlog video you can see the marker being in the middle of the li “no css allowed”, which is what I fixed.This was a pretty small devlog because it was a small update, but still something I wanted to share.
Log in to leave a comment

The site being rendered in the screenshot is the flavorless website, which links to sans style.
Log in to leave a comment

<a> tags. This was pretty simple because of my existing infra.Agent which coordinates all the browser’s jobs. This will make it easier to add things like navigation with links later (and a whole lot more, that I’m not thinking about right now!).Transfer-Encoding: chunked. Spent some time understanding how that even works, but it was a not-so-difficult implementation after that. I only found out this existed because I tried to render example.com, which uses chunked encoding.vh and vw units in CSS. This was kinda annoying because it was a lot of refactoring of my functions to accept the viewport size. It’s not even all that rewarding tbh. But whatever, it’ll be useful later.The screenshot is my browser’s attempt at rendering example.com. You’ll notice that the text isn’t centered properly. This is because they use margin: 15vh auto, where the auto is supposed to center the element horizontally. I haven’t implemented that yet.

h1 { font-size: 2em; margin: 1em; }, the em unit for margin is relative to the font size of the element itself, while for font-size, it is relative to the font size of the parent element. My code however used only the parent element’s font size for both cases, which was incorrect. A big ass refactor of my value resolution code later, and this can now be respected.<ul>s (and specifically the <li>s inside) look, the result is almost indistinguishable from a modern browser!
The attachment shows Zen (left) and Harbor (right) rendering assets/html/custom003.html. The only differences I can see are pixely text (I need antialiasing) and links not having underlines!

(As first promised 16 devlogs ago)
H, T, E, etc.). Curved glyphs were giving weird artifacts when filled, so I had to leave them out.S, C, but not O, A, etc.)O, A, etc.)I told you I’d eventually get around to filling glyphs :P (even if I am 16 devlogs and 20 days late)

position property. Pretty basic.position: absolute support. This was a bit tricky because I had to deal with respecting position of different parents depending on the position property.position: relative support. This was easier than absolute positioning since I could just offset the element from its normal position.I’ve left out a few commits because between this devlog and the last there were ~12 commits that were mostly minor changes, bug fixes, etc. Also I feel like the browser is really starting to come together - the screenshot looks so good.
Log in to leave a comment

(Holy refactor)
larger and smaller size keywords for CSS.This was the most painful refactor I’ve done in my life. Wouldn’t wish it upon my worst enemy.
Log in to leave a comment

br tags by adding a flag for whether or not the layout engine is requesting a line switch. That works ok, but there seem to be a ton of other bugs that I need to fix (as you can probably tell from the photo)This entire devlog was just me fixing my shit codebase :yay: also ts was a smaller devlog than what I normally post because I’m super tired today. Really could not be bothered to fix the bugs today before posting this devlog.
Log in to leave a comment

(YAY)
lis were being positioned super incorrectly (the box was ~20px to the left of where it should be). I discovered this because I was trying to implement hovering and added a background-color to li when hovered, which showed the box and the text were not aligned.h1:hover {
color: red;
}
Now actually makes h1s red only on hover.
This was a ton of work, the basis of hovering (detecting which elements are hovered) was pretty easy but applying styles only on hover was the bulk of this devlog.

<p><a>Link</a> to something</p> being rendered without a space between “Link” and “to”. Fixed it by trimming left or right whitespace only when the child is the first or last child, respectively.list-item display type, which is used for lis. This took SO long because I tried to make it once, and it just refused to work for some reason. So I stashed all my changes and started over from scratch, and it worked after a while. I ran into a bug where bullets where being rendered as ellipses rather than circles, which I fixed by properly converting to clips space.The screenshot in this devlog is from jmeow.net! The last bit of JavaScript being rendered is because my browser doesn’t understand that script tags shouldn’t be rendered.
Log in to leave a comment

ttcs instead of individual ttf data so that bold and italics are possible. With this possibility, I implemented bold font weights.<b> and <i> tags now.margin CSS property. Also added basic support for margin-* properties (margin-top, margin-bottom, margin-left, margin-right). I added these margins onto headings in the ua.css. The style rule for h1 now looks like this:h1 {
display: block;
font-size: 2em;
font-weight: bold;
margin: 0.67em 0;
}
<b> and <i> tags.
font properties to my CSS, so I started with creating the structures required for parsing a font declaration into an object.font declarations. This was pretty similar to how I implemented parsing for background declarations.assets/css/ua.css is the user agent stylesheet that Harbor uses by default.rem and em units. I realize as I’m writing this devlog that this means that smaller and larger keywords should also work with minimal implementation.display block and inline. Reworking the layout engine to support block and inline elements properly was really annoying because of how messy my code was (what the hell was I thinking when I wrote it??).AccentColor, etc.) in my color parser, so I added support for those.The site being rendered in the screenshot is old.arson.dev, which is my old personal website.
Log in to leave a comment

background-color is a subproperty of background-* properties, so I refactored the codebase to accommodate that.background properties should be parsed, so I implemented that. Also turns out I severely misread the spec when I was first refactoring the codebase last commit and got a very wrong idea of what a background layer is. So I had to do a lot of refactoring of that too.backgroundsub-properties (like background-color, background-image etc).I’m super locked in for my exams rn, so updates might be a bit slow. But I’ll try to push as much as I can.
Log in to leave a comment

(Holy shit)
</style> it automatically reads the contents of the style tag and tries to parse it as a CSSStyleSheet, and adds it to a vec of stylesheetsComputedStyle struct which stores data about the actual styles that an element should use when rendered. I used a depth-first traversal to go through the DOM and try to compute styles based on selector matches for every element.Crazy progress + this devlog marks 100hr milestone!

(Finally something to show)
Let parsed rule be the result of parsing rule according to the appropriate CSS specifications
With no further explanation. After digging I found that this meant I needed to implement parsing for individual rule kinds , so I started with that for the classic style rule. So, I started implementing a selector parser.
Log in to leave a comment

(What do I even upload as the attachment)
This is probably one of the most boring updates I’ve ever written. CSS parsing is just not very exciting. It’s a whole lot of reading through poorly written specs with conflicting wording and trying to implement them. It’s so boring that I don’t really have anything to show for it.
Log in to leave a comment

Log in to leave a comment

tests/ directory so I have a way to verify that my code is working as expected.The image has text magnified to 2.5x the normal size for better readability.
Log in to leave a comment

Log in to leave a comment

(Where’d all the time go come from?)
I feel like I didn’t spend THAT much time on this devlog, but looking back at it I did add a whole lot of features. I have no idea why I didn’t commit more often, but it might have something to do with the fact that work on this commit started at 1 AM.
Log in to leave a comment

(My favorite devlog so far)
glyf table. There was barely anything done correctly when I first wrote it, but I had no way of telling until I tried rendering. This took WAY TOO LONG. I think the core of the issue was that x- and y-coordinates are stored as deltas, not absolute values. I refactored the codebase to hold contours rather than raw points, so that may have somehow solved part of the issue too.LineList rather than a LineStrip to render the contours properly. A LineStrip connects the last point to the first point automatically, which is not what I wanted (it would connect points from different contours).
post and loca tables. I uniquely remember enjoying writing the post table’s code, I don’t know why. loca was pretty boring.glyf. This table was a pain to implement. I had to individually implement both simple and composite glyphs. Composites were especially tricky because there wasn’t a well defined structure for them in the docs.cvt, fpgm, prep, gasp and meta. I’m almost done adding tables - there’s only a couple left (4 more that I plan on implementing).hdmx, kern, vhea, vmtx.bsln, feat, etc. (probably won’t)
(Micro devlog)
OS/2 and name tables, though the name table parsing only supports version 0 currently.post table parsing.I really only made this devlog because it pushes me over the 60h mark for this project.

head parsing.hhea, maxp and hmtx tables. This was actually a lot harder than I expected because the hmtx table’s length depends on variables defined in the maxp and hhea tables - but it’s not guaranteed that those tables will come before hmtx in the file. So I had to set up a deferred parsing system which allowed me to defer parsing of certain tables until their dependencies were parsed.name, OS/2, post.loca and glyf.Log in to leave a comment

/System/Library/Fonts/Times.ttc as my test font file for now.Log in to leave a comment

(Small, but extremely significant change)
_adoption_agency function which means a tags (along with all the other formatting tags) are now being properly serialized. Honestly I’m super sceptical about this implementation but it seems to be working fine for now. I’m too scared to test it.Log in to leave a comment
If you can’t read the last lines, here’s what they say:
“Document Tree:
If printed, the DOM would be 254647 characters long.
Extra dev note: I manually went through the DOM and can confirm it looks correct.”

(This devlog is mostly refactoring)
Every commit in this devlog had at least some work on the tree constructor even if not explicitly mentioned in the change notes.
Log in to leave a comment

list of active formatting elements, so I finally added it here. It was a lot more annoying that I thought it would be.The attachment is ~90 lines of an almost 300 line long tree structure that I use to represent the DOM!
Log in to leave a comment

(I keep forgetting to devlog)
_appropriate_insertion_place). Refactoring my code to support this function was very satisfying.Log in to leave a comment

(This was a LONG coding session)
The core theme of this devlog is the implementation of the HTML Tokenization spec - comprising an absolutely enormous state machine with 80 different possible states, at least 40 different kinds of errors, and extremely poor documentation on specifics. Commit-wise breakdowns are pretty uneventful but out of habit I’ll do it anyway - but with no details.
This was probably the most boring coding session I’ve ever gone through. The code, like the specification, is repetitive, and can probably be modularized to oblivion. But if you think I’m voluntary touching those 2600 lines of code EVER again, you’re crazy.
(Edit: Changed a commit link that was broken after commit reword)
Log in to leave a comment

The next devlog is probably gonna be a BIG one, like 5+ hours minimum (most likely) - given the fact that the HTML parser specification is just so LONG. I’ve been reading through it and the main state machine literally has 80 states and that’s not even the entire specification because there’s probably a billion utility functions and side quests I’ll have to go on to implement it right.
Log in to leave a comment

(This was a LONG coding session)
Log in to leave a comment

Log in to leave a comment

Maybe trying to start with expressing the entire HTML spec in code wasn’t the best idea. I don’t like leaving things mid-way but I don’t really see another way out here, at least for now.
Log in to leave a comment

Log in to leave a comment

Log in to leave a comment

0784aeb: Started work on the HTTP client that will be sending requests and receiving responses. The next commit should have the actual sending moved into a separate function so that different HTTP Protocols can send requests differently.
ba895e8: I added integrity checks to requests, different send methods depending on protocol, basic response decoding (only enough for HTTP/0.9 for now), and a basic Client struct so you can specify an address once and send requests to it multiple times
Log in to leave a comment
Harbor Browser is going to be a browser where I write every major service myself. This means everything from:
And anything else a traditional browser would have must be written by me. I’m trying to limit myself to as few dependencies as possible.
I decided to use winit and wgpu as the windowing and GPU abstraction layers. They’ll be doing a lot of the heavy lifting for this project, I suspect. I got a basic window opening and cleared with a color. It doesn’t sound like much but that was 250 lines of code :|